No public description

PiperOrigin-RevId: 568641066
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 2e04944..e150612 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -39,6 +39,7 @@
 
 cc_library(
     name = "fuzztest",
+    testonly = True,
     srcs = ["fuzztest.cc"],
     hdrs = ["fuzztest.h"],
     deps = [
@@ -67,6 +68,8 @@
     hdrs = ["init_fuzztest.h"],
     deps = [
         ":googletest_adaptor",
+        ":io",
+        ":logging",
         ":registry",
         ":runtime",
         "@com_google_absl//absl/flags:flag",
@@ -126,6 +129,7 @@
     srcs = ["internal/any_test.cc"],
     deps = [
         ":any",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -133,6 +137,7 @@
 
 cc_library(
     name = "centipede_adaptor",
+    testonly = True,
     srcs = ["internal/centipede_adaptor.cc"],
     hdrs = ["internal/centipede_adaptor.h"],
     defines = ["FUZZTEST_USE_CENTIPEDE"],
@@ -142,12 +147,14 @@
         ":logging",
         ":runtime",
         "@com_google_absl//absl/algorithm:container",
+        "@com_google_absl//absl/strings:string_view",
         "@com_google_fuzztest//centipede:centipede_runner_no_main",
     ],
 )
 
 cc_library(
     name = "compatibility_mode",
+    testonly = True,
     srcs = ["internal/compatibility_mode.cc"],
     hdrs = ["internal/compatibility_mode.h"],
     deps = [
@@ -291,6 +298,7 @@
         ":fixture_driver",
         ":logging",
         ":registration",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/types:span",
         "@com_google_googletest//:gtest_main",
     ],
@@ -304,6 +312,8 @@
     deps = [
         ":registry",
         ":runtime",
+        "@com_google_absl//absl/functional:function_ref",
+        "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest",
     ],
 )
@@ -335,6 +345,7 @@
     deps = [
         ":fuzztest",
         ":io",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
     ],
@@ -378,12 +389,14 @@
         ":type_support",
         "@com_google_absl//absl/functional:any_invocable",
         "@com_google_absl//absl/strings:str_format",
+        "@com_google_absl//absl/strings:string_view",
         "@com_google_absl//absl/types:span",
     ],
 )
 
 cc_library(
     name = "registry",
+    testonly = True,
     srcs = ["internal/registry.cc"],
     hdrs = ["internal/registry.h"],
     deps = [
@@ -435,6 +448,7 @@
         ":domain",
         ":runtime",
         ":test_protobuf_cc_proto",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/time",
         "@com_google_googletest//:gtest_main",
     ],
@@ -457,6 +471,7 @@
     srcs = ["internal/seed_seq_test.cc"],
     deps = [
         ":seed_seq",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/types:span",
         "@com_google_googletest//:gtest_main",
@@ -482,6 +497,7 @@
     deps = [
         ":serialization",
         ":test_protobuf_cc_proto",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_googletest//:gtest_main",
         "@com_google_protobuf//:protobuf",
     ],
@@ -515,6 +531,7 @@
     srcs = ["internal/subprocess_test.cc"],
     deps = [
         ":subprocess",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/time",
         "@com_google_googletest//:gtest_main",
@@ -536,8 +553,8 @@
     name = "table_of_recent_compares_test",
     srcs = ["internal/table_of_recent_compares_test.cc"],
     deps = [
-        ":domain",
         ":table_of_recent_compares",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/random",
         "@com_google_googletest//:gtest_main",
     ],
@@ -575,6 +592,7 @@
         ":domain",
         ":test_protobuf_cc_proto",
         ":type_support",
+        "@com_google_absl//absl/functional:function_ref",
         "@com_google_absl//absl/numeric:int128",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/strings:str_format",
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc
index 9d05df0..9592ee3 100644
--- a/fuzztest/init_fuzztest.cc
+++ b/fuzztest/init_fuzztest.cc
@@ -10,10 +10,14 @@
 #include "gtest/gtest.h"
 #include "absl/flags/flag.h"
 #include "absl/strings/match.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
+#include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
 #include "absl/time/time.h"
 #include "./fuzztest/internal/googletest_adaptor.h"
+#include "./fuzztest/internal/io.h"
+#include "./fuzztest/internal/logging.h"
 #include "./fuzztest/internal/registry.h"
 #include "./fuzztest/internal/runtime.h"
 
@@ -124,6 +128,22 @@
     std::exit(0);
   }
 
+  const std::vector<std::string_view> binary_parts =
+      absl::StrSplit(*argv[0], "_replay_");
+  if (CheckCorpus) {
+    CheckCorpus(binary_parts[0]);
+  }
+
+  FUZZTEST_INTERNAL_CHECK(
+      internal::Runtime::instance().files_to_replay().empty(),
+      "You cannot manually set corpus files");
+  std::vector<std::string> corpus_files = internal::GetFileOrFilesInDir(
+      absl::StrCat("security/laser/sundew/blaze/corpora/", binary_parts[0],
+                   "/minimized"));
+  if (!corpus_files.empty()) {
+    internal::Runtime::instance().SetFilesToReplay(corpus_files);
+  }
+
   const auto test_to_fuzz = absl::GetFlag(FUZZTEST_FLAG(fuzz));
   const bool is_test_to_fuzz_specified = test_to_fuzz != kUnspecified;
   if (is_test_to_fuzz_specified) {
@@ -140,7 +160,16 @@
     internal::Runtime::instance().SetFuzzTimeLimit(duration);
   }
 
-  internal::RegisterFuzzTestsAsGoogleTests(argc, argv);
+  std::vector<std::string> crashing_files;
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+  const bool reproduce_crashes = binary_parts.size() > 1;
+  if (reproduce_crashes) {
+    crashing_files = internal::GetFileOrFilesInDir(
+        absl::StrCat("security/laser/sundew/blaze/corpora/", binary_parts[0],
+                     "/crashing_inputs"));
+  }
+#endif
+  internal::RegisterFuzzTestsAsGoogleTests(argc, argv, crashing_files);
 
   const RunMode run_mode = is_test_to_fuzz_specified || is_duration_specified
                                ? RunMode::kFuzz
diff --git a/fuzztest/init_fuzztest.h b/fuzztest/init_fuzztest.h
index 547e7ff..8e5f57a 100644
--- a/fuzztest/init_fuzztest.h
+++ b/fuzztest/init_fuzztest.h
@@ -54,6 +54,8 @@
 // REQUIRES: Binary must be built with SanCov instrumentation on.
 void RunSpecifiedFuzzTest(std::string_view name);
 
+void __attribute__((weak)) CheckCorpus(std::string_view corpus);
+
 }  // namespace fuzztest
 
 #endif  // FUZZTEST_FUZZTEST_INIT_FUZZTEST_H_
diff --git a/fuzztest/internal/any_test.cc b/fuzztest/internal/any_test.cc
index d073fc3..6ea51d2 100644
--- a/fuzztest/internal/any_test.cc
+++ b/fuzztest/internal/any_test.cc
@@ -20,9 +20,13 @@
 #include <utility>
 
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/strings/str_cat.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 TEST(CopyableAny, BasicOperationsWork) {
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc
index be8fd66..16a5e6f 100644
--- a/fuzztest/internal/centipede_adaptor.cc
+++ b/fuzztest/internal/centipede_adaptor.cc
@@ -19,6 +19,7 @@
 #include <cstring>
 #include <functional>
 #include <memory>
+#include <optional>
 #include <random>
 #include <string>
 #include <thread>
@@ -26,6 +27,7 @@
 #include <vector>
 
 #include "absl/algorithm/container.h"
+#include "absl/strings/string_view.h"
 #include "./centipede/runner_interface.h"
 #include "./fuzztest/internal/domains/domain_base.h"
 #include "./fuzztest/internal/logging.h"
@@ -154,9 +156,10 @@
     const FuzzTest& test, std::unique_ptr<UntypedFixtureDriver> fixture_driver)
     : test_(test), fuzzer_impl_(test_, std::move(fixture_driver)) {}
 
-void CentipedeFuzzerAdaptor::RunInUnitTestMode() {
+void CentipedeFuzzerAdaptor::RunInUnitTestMode(
+    std::optional<absl::string_view> input) {
   // Run the unit test mode directly without using Centipede.
-  fuzzer_impl_.RunInUnitTestMode();
+  fuzzer_impl_.RunInUnitTestMode(input);
 }
 
 int CentipedeFuzzerAdaptor::RunInFuzzingMode(int* argc, char*** argv) {
diff --git a/fuzztest/internal/centipede_adaptor.h b/fuzztest/internal/centipede_adaptor.h
index 4ce3033..e0d256f 100644
--- a/fuzztest/internal/centipede_adaptor.h
+++ b/fuzztest/internal/centipede_adaptor.h
@@ -27,7 +27,7 @@
  public:
   CentipedeFuzzerAdaptor(const FuzzTest& test,
                          std::unique_ptr<UntypedFixtureDriver> fixture_driver);
-  void RunInUnitTestMode() override;
+  void RunInUnitTestMode(std::optional<absl::string_view> input) override;
   int RunInFuzzingMode(int* argc, char*** argv) override;
 
  private:
diff --git a/fuzztest/internal/fixture_driver_test.cc b/fuzztest/internal/fixture_driver_test.cc
index 8459ade..fd6ec6a 100644
--- a/fuzztest/internal/fixture_driver_test.cc
+++ b/fuzztest/internal/fixture_driver_test.cc
@@ -19,6 +19,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/types/span.h"
 #include "./fuzztest/domain_core.h"
 #include "./fuzztest/internal/any.h"
@@ -26,6 +27,9 @@
 #include "./fuzztest/internal/registration.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 using ::testing::UnorderedElementsAre;
diff --git a/fuzztest/internal/googletest_adaptor.cc b/fuzztest/internal/googletest_adaptor.cc
index e2c2952..eb30ecd 100644
--- a/fuzztest/internal/googletest_adaptor.cc
+++ b/fuzztest/internal/googletest_adaptor.cc
@@ -1,18 +1,36 @@
 #include "./fuzztest/internal/googletest_adaptor.h"
 
+#include <optional>
+#include <string>
+#include <vector>
+
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
+#include "absl/strings/str_cat.h"
 #include "./fuzztest/internal/registry.h"
 
 namespace fuzztest::internal {
 
-void RegisterFuzzTestsAsGoogleTests(int* argc, char*** argv) {
+void RunExpectExit(absl::FunctionRef<void()> test) {
+#if defined(__APPLE__) || defined(_MSC_VER)
+  test();
+#else
+  EXPECT_EXIT(test(), ::testing::ExitedWithCode(0), "");
+#endif
+}
+
+#define run
+void RegisterFuzzTestsAsGoogleTests(
+    int* argc, char*** argv, const std::vector<std::string>& crashing_inputs) {
   ::fuzztest::internal::ForEachTest([&](auto& test) {
     auto fixture_factory =
         [argc, argv, &test]() -> ::fuzztest::internal::GTest_TestAdaptor* {
-      return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv);
+      return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv,
+                                                         std::nullopt);
     };
     auto test_factory = [argc, argv, &test]() -> ::testing::Test* {
-      return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv);
+      return new ::fuzztest::internal::GTest_TestAdaptor(test, argc, argv,
+                                                         std::nullopt);
     };
     if (test.uses_fixture()) {
       ::testing::RegisterTest(test.suite_name(), test.test_name(), nullptr,
diff --git a/fuzztest/internal/googletest_adaptor.h b/fuzztest/internal/googletest_adaptor.h
index 9b96d22..64b99d9 100644
--- a/fuzztest/internal/googletest_adaptor.h
+++ b/fuzztest/internal/googletest_adaptor.h
@@ -17,9 +17,11 @@
 
 #include <cstdlib>
 #include <memory>
+#include <optional>
 #include <utility>
 
 #include "gtest/gtest.h"
+#include "absl/strings/string_view.h"
 #include "./fuzztest/internal/registry.h"
 #include "./fuzztest/internal/runtime.h"
 
@@ -27,13 +29,18 @@
 
 class GTest_TestAdaptor : public ::testing::Test {
  public:
-  explicit GTest_TestAdaptor(FuzzTest& test, int* argc, char*** argv)
-      : test_(test), argc_(argc), argv_(argv) {}
+  explicit GTest_TestAdaptor(FuzzTest& test, int* argc, char*** argv,
+                             std::optional<absl::string_view> input)
+      : test_(test), argc_(argc), argv_(argv), input_(input) {}
 
   void TestBody() override {
-    auto test = std::move(test_).make();
+    std::cout << "In Test Body..." << std::endl;
+    std::cout << "unit test? "
+              << (Runtime::instance().run_mode() == RunMode::kUnitTest)
+              << std::endl;
+    auto test = test_.make();
     if (Runtime::instance().run_mode() == RunMode::kUnitTest) {
-      test->RunInUnitTestMode();
+      test->RunInUnitTestMode(input_);
     } else {
       ASSERT_EQ(0, test->RunInFuzzingMode(argc_, argv_)) << "Fuzzing failure.";
     }
@@ -55,6 +62,7 @@
   FuzzTest& test_;
   int* argc_;
   char*** argv_;
+  std::optional<absl::string_view> input_;
 };
 
 template <typename Base, typename TestPartResult>
@@ -77,7 +85,8 @@
 };
 
 // Registers FUZZ_TEST as GoogleTest TEST-s.
-void RegisterFuzzTestsAsGoogleTests(int* argc, char*** argv);
+void RegisterFuzzTestsAsGoogleTests(
+    int* argc, char*** argv, const std::vector<std::string>& crashing_inputs);
 
 }  // namespace fuzztest::internal
 
diff --git a/fuzztest/internal/io.cc b/fuzztest/internal/io.cc
index 14dc4b2..6804f72 100644
--- a/fuzztest/internal/io.cc
+++ b/fuzztest/internal/io.cc
@@ -65,6 +65,10 @@
   FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS");
 }
 
+std::vector<std::string> GetFileOrFilesInDir(std::string_view file_or_dir) {
+  FUZZTEST_INTERNAL_CHECK(false, "Can't replay in iOS/MacOS");
+}
+
 #else  // defined(__APPLE__)
 
 bool WriteFile(std::string_view filename, std::string_view contents) {
@@ -134,6 +138,36 @@
   return out;
 }
 
+namespace {
+
+void GetAllFilesRecursively(std::string_view dir,
+                            std::vector<std::string>& files) {
+  std::vector<std::string> entries = ListDirectory(dir);
+  for (const auto& entry : entries) {
+    absl::FPrintF(GetStderr(), "entry: %s\n", entry);
+    if (!std::filesystem::is_directory(entry)) {
+      files.push_back(entry);
+    } else {
+      GetAllFilesRecursively(entry, files);
+    }
+  }
+}
+
+}  // namespace
+
+std::vector<std::string> GetFileOrFilesInDir(std::string_view file_or_dir) {
+  // Try as a directory path first.
+  std::vector<std::string> files;
+  GetAllFilesRecursively(file_or_dir, files);
+  // If not, consider it a file path.
+  if (files.empty()) {
+    if (std::filesystem::is_regular_file(file_or_dir)) {
+      files.push_back(std::string(file_or_dir));
+    }
+  }
+  return files;
+}
+
 #endif  // defined(STUB_FILESYSTEM)
 
 }  // namespace fuzztest::internal
diff --git a/fuzztest/internal/io.h b/fuzztest/internal/io.h
index 22e12b5..84365fe 100644
--- a/fuzztest/internal/io.h
+++ b/fuzztest/internal/io.h
@@ -46,6 +46,8 @@
 // returns an empty list.
 std::vector<std::string> ListDirectory(std::string_view dir);
 
+std::vector<std::string> GetFileOrFilesInDir(std::string_view file_or_dir);
+
 }  // namespace fuzztest::internal
 
 #endif  // FUZZTEST_FUZZTEST_INTERNAL_IO_H_
diff --git a/fuzztest/internal/io_test.cc b/fuzztest/internal/io_test.cc
index 98841f1..7029e5a 100644
--- a/fuzztest/internal/io_test.cc
+++ b/fuzztest/internal/io_test.cc
@@ -27,10 +27,14 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/strings/str_cat.h"
 #include "./fuzztest/fuzztest.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 using ::testing::Eq;
diff --git a/fuzztest/internal/registration.h b/fuzztest/internal/registration.h
index c9e032c..7b0e940 100644
--- a/fuzztest/internal/registration.h
+++ b/fuzztest/internal/registration.h
@@ -26,6 +26,7 @@
 
 #include "absl/functional/any_invocable.h"
 #include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "./fuzztest/domain.h"
 #include "./fuzztest/internal/domains/aggregate_of_impl.h"
@@ -41,6 +42,7 @@
   const char* file = nullptr;
   int line = 0;
   bool uses_fixture = false;
+  std::function<void*(absl::string_view)> factory;
 };
 
 // Use base classes to progressively add members/behavior to the registerer
diff --git a/fuzztest/internal/registry.h b/fuzztest/internal/registry.h
index a169308..699d1e9 100644
--- a/fuzztest/internal/registry.h
+++ b/fuzztest/internal/registry.h
@@ -15,6 +15,8 @@
 #ifndef FUZZTEST_FUZZTEST_INTERNAL_REGISTRY_H_
 #define FUZZTEST_FUZZTEST_INTERNAL_REGISTRY_H_
 
+#include <stdbool.h>
+
 #include <memory>
 #include <string_view>
 #include <type_traits>
diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc
index d2089db..041245e 100644
--- a/fuzztest/internal/runtime.cc
+++ b/fuzztest/internal/runtime.cc
@@ -57,6 +57,8 @@
 
 namespace fuzztest::internal {
 
+extern void RunExpectExit(absl::FunctionRef<void()> test);
+
 void (*crash_handler_hook)();
 
 void Runtime::DumpReproducer(std::string_view outdir) const {
@@ -315,9 +317,8 @@
 }
 
 bool FuzzTestFuzzerImpl::ReplayInputsIfAvailable() {
-  runtime_.SetRunMode(RunMode::kFuzz);
-
-  if (const auto file_paths = GetFilesToReplay()) {
+  if (bool early_return;
+      const auto file_paths = GetFilesToReplay(early_return)) {
     for (const std::string& path : *file_paths) {
       const auto content = ReadFile(path);
       if (!content) {
@@ -337,9 +338,10 @@
       absl::FPrintF(GetStderr(), "[.] Replaying %s\n", path);
       RunOneInput({*corpus_value});
     }
-    return true;
+    if (early_return) return true;
   }
 
+  runtime_.SetRunMode(RunMode::kFuzz);
   if (const auto to_minimize = ReadReproducerToMinimize()) {
     absl::FPrintF(GetStderr(),
                   "[!] Looking for smaller mutations indefinitely: please "
@@ -386,16 +388,17 @@
   return false;
 }
 
-std::optional<std::vector<std::string>> FuzzTestFuzzerImpl::GetFilesToReplay() {
+std::optional<std::vector<std::string>> FuzzTestFuzzerImpl::GetFilesToReplay(
+    bool& early_return) {
   auto file_or_dir = absl::NullSafeStringView(getenv("FUZZTEST_REPLAY"));
-  if (file_or_dir.empty()) return std::nullopt;
-  // Try as a directory path first.
-  std::vector<std::string> files = ListDirectory(std::string(file_or_dir));
-  // If not, consider it a file path.
-  if (files.empty()) {
-    files.push_back(std::string(file_or_dir));
+  if (file_or_dir.empty()) {
+    early_return = false;
+    auto result = Runtime::instance().files_to_replay();
+    if (result.empty()) return std::nullopt;
+    return result;
   }
-  return files;
+  early_return = true;
+  return GetFileOrFilesInDir(file_or_dir);
 }
 
 std::optional<corpus_type> FuzzTestFuzzerImpl::ReadReproducerToMinimize() {
@@ -599,12 +602,36 @@
   }
 }
 
-void FuzzTestFuzzerImpl::RunInUnitTestMode() {
+void FuzzTestFuzzerImpl::RunInUnitTestMode(
+    std::optional<absl::string_view> input) {
+  // std::cout << "In UnitTest mode" << std::endl;
   fixture_driver_->SetUpFuzzTest();
   [&] {
     runtime_.EnableReporter(&stats_, [] { return absl::Now(); });
     runtime_.SetCurrentTest(&test_);
 
+    if (input.has_value()) {
+      // std::cout << "has crashing input!" << std::endl;
+      const auto content = ReadFile(*input);
+      if (!content) {
+        absl::FPrintF(GetStderr(),
+                      "[!] Failed to read FUZZTEST_REPLAY file or directory "
+                      "(might be empty): %s\n",
+                      *input);
+      } else {
+        auto corpus_value = TryParse(*content);
+        if (!corpus_value) {
+          absl::FPrintF(GetStderr(),
+                        "[!] Skipping invalid input file %s.\n===\n%s\n===\n",
+                        *input, *content);
+        } else {
+          absl::FPrintF(GetStderr(), "[.] Replaying %s\n", *input);
+          RunOneInput({*corpus_value}, /*expect_pass=*/true);
+        }
+      }
+      return;
+    }
+
     // TODO(sbenzaquen): Currently, some infrastructure code assumes that replay
     // works in unit test mode, so we support it. However, we would like to
     // limit replaying to fuzzing mode only, where we can guarantee that only
@@ -660,7 +687,7 @@
 }
 
 FuzzTestFuzzerImpl::RunResult FuzzTestFuzzerImpl::RunOneInput(
-    const Input& input) {
+    const Input& input, bool expect_pass) {
   ++stats_.runs;
   auto untyped_args = params_domain_->UntypedGetValue(input.args);
   Runtime::Args debug_args{input.args, *params_domain_};
@@ -681,7 +708,11 @@
   }
 
   fixture_driver_->SetUpIteration();
-  fixture_driver_->Test(std::move(untyped_args));
+  if (expect_pass) {
+    RunExpectExit([&]() { fixture_driver_->Test(std::move(untyped_args)); });
+  } else {
+    fixture_driver_->Test(std::move(untyped_args));
+  }
   fixture_driver_->TearDownIteration();
   if (execution_coverage_ != nullptr) {
     execution_coverage_->SetIsTracing(false);
diff --git a/fuzztest/internal/runtime.h b/fuzztest/internal/runtime.h
index 085963f..d430014 100644
--- a/fuzztest/internal/runtime.h
+++ b/fuzztest/internal/runtime.h
@@ -64,7 +64,7 @@
 class FuzzTestFuzzer {
  public:
   virtual ~FuzzTestFuzzer() = default;
-  virtual void RunInUnitTestMode() = 0;
+  virtual void RunInUnitTestMode(std::optional<absl::string_view> input) = 0;
   // Returns fuzzing mode's exit code. Zero indicates success.
   virtual int RunInFuzzingMode(int* argc, char*** argv) = 0;
 };
@@ -151,6 +151,15 @@
   }
   absl::Duration fuzz_time_limit() const { return fuzz_time_limit_; }
 
+  void SetFilesToReplay(absl::Span<const std::string> files_to_replay) {
+    FUZZTEST_INTERNAL_CHECK(files_to_replay_.empty(),
+                            "Replay files are set already!");
+    files_to_replay_ = std::vector<std::string>(files_to_replay.begin(),
+                                                files_to_replay.end());
+  }
+
+  std::vector<std::string>& files_to_replay() { return files_to_replay_; }
+
   void EnableReporter(const RuntimeStats* stats, absl::Time (*clock_fn)()) {
     reporter_enabled_ = true;
     stats_ = stats;
@@ -200,6 +209,7 @@
 
   RunMode run_mode_ = RunMode::kUnitTest;
   absl::Duration fuzz_time_limit_ = absl::InfiniteDuration();
+  std::vector<std::string> files_to_replay_;
 
   bool reporter_enabled_ = false;
   Args* current_args_ = nullptr;
@@ -230,7 +240,7 @@
 
  private:
   // TODO(fniksic): Refactor to reduce code complexity and improve readability.
-  void RunInUnitTestMode() override;
+  void RunInUnitTestMode(std::optional<absl::string_view> input) override;
 
   // TODO(fniksic): Refactor to reduce code complexity and improve readability.
   int RunInFuzzingMode(int* argc, char*** argv) override;
@@ -255,7 +265,7 @@
 
   bool ReplayInputsIfAvailable();
 
-  std::optional<std::vector<std::string>> GetFilesToReplay();
+  std::optional<std::vector<std::string>> GetFilesToReplay(bool& early_return);
 
   std::optional<corpus_type> ReadReproducerToMinimize();
 
@@ -291,7 +301,7 @@
 
   void InitializeCorpus(absl::BitGenRef prng);
 
-  RunResult RunOneInput(const Input& input);
+  RunResult RunOneInput(const Input& input, bool expect_pass = false);
 
   bool ShouldStop();
 
diff --git a/fuzztest/internal/runtime_test.cc b/fuzztest/internal/runtime_test.cc
index 0fbbad9..c063ea2 100644
--- a/fuzztest/internal/runtime_test.cc
+++ b/fuzztest/internal/runtime_test.cc
@@ -19,11 +19,14 @@
 #include <tuple>
 
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/time/time.h"
 #include "./fuzztest/domain.h"
 #include "./fuzztest/internal/test_protobuf.pb.h"
 
 namespace fuzztest::internal {
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 TEST(OnFailureTest, Output) {
diff --git a/fuzztest/internal/seed_seq_test.cc b/fuzztest/internal/seed_seq_test.cc
index 5f9ed7a..6208823 100644
--- a/fuzztest/internal/seed_seq_test.cc
+++ b/fuzztest/internal/seed_seq_test.cc
@@ -26,12 +26,16 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "absl/strings/strip.h"
 #include "absl/types/span.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 using ::testing::ElementsAre;
diff --git a/fuzztest/internal/serialization_test.cc b/fuzztest/internal/serialization_test.cc
index 7780f9b..ba09c1f 100644
--- a/fuzztest/internal/serialization_test.cc
+++ b/fuzztest/internal/serialization_test.cc
@@ -30,11 +30,15 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "./fuzztest/internal/test_protobuf.pb.h"
 #include "google/protobuf/text_format.h"
 #include "google/protobuf/util/message_differencer.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 using testing::_;
diff --git a/fuzztest/internal/subprocess_test.cc b/fuzztest/internal/subprocess_test.cc
index bfb3c3a..146bdd2 100644
--- a/fuzztest/internal/subprocess_test.cc
+++ b/fuzztest/internal/subprocess_test.cc
@@ -20,10 +20,14 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/strings/str_cat.h"
 #include "absl/time/time.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 using ::testing::HasSubstr;
diff --git a/fuzztest/internal/table_of_recent_compares_test.cc b/fuzztest/internal/table_of_recent_compares_test.cc
index 9178744..01146c3 100644
--- a/fuzztest/internal/table_of_recent_compares_test.cc
+++ b/fuzztest/internal/table_of_recent_compares_test.cc
@@ -7,9 +7,13 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/random/random.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 // TODO(JunyangShao) : Make these functions neater (https://abseil.io/tips/122).
diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc
index 9a119ba..fd626de 100644
--- a/fuzztest/internal/type_support_test.cc
+++ b/fuzztest/internal/type_support_test.cc
@@ -35,6 +35,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/functional/function_ref.h"
 #include "absl/numeric/int128.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -44,6 +45,9 @@
 #include "./fuzztest/internal/test_protobuf.pb.h"
 
 namespace fuzztest::internal {
+
+void RunExpectExit(absl::FunctionRef<void()> test) { test(); }
+
 namespace {
 
 using ::testing::AllOf;