Provide reproduction command when a reproducer is requested.

PiperOrigin-RevId: 690702036
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index 9b0aed5..f18f4a9 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -761,6 +761,11 @@
   ASSERT_TRUE(parsed) << std_err;
   auto args = parsed->ToCorpus<std::tuple<std::string>>();
   EXPECT_THAT(args, Optional(FieldsAre(StartsWith("Fuzz")))) << std_err;
+  EXPECT_THAT(std_err,
+              HasSubstr(absl::StrCat("Reproducer file was dumped at:\n",
+                                     replay_files[0].path)));
+  EXPECT_THAT(std_err, HasSubstr(absl::StrCat("--test_env=FUZZTEST_REPLAY=",
+                                              replay_files[0].path)));
 }
 
 TEST_F(FuzzingModeCommandLineInterfaceTest, SavesCorpusWhenEnvVarIsSet) {
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 6829abe..65dcf27 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -623,9 +623,12 @@
     name = "runtime_test",
     srcs = ["internal/runtime_test.cc"],
     deps = [
+        ":configuration",
         ":domain_core",
+        ":flag_name",
         ":runtime",
         ":test_protobuf_cc_proto",
+        "@com_google_absl//absl/flags:flag",
         "@com_google_absl//absl/strings",
         "@com_google_absl//absl/time",
         "@com_google_googletest//:gtest_main",
diff --git a/fuzztest/CMakeLists.txt b/fuzztest/CMakeLists.txt
index 834c254..5091c51 100644
--- a/fuzztest/CMakeLists.txt
+++ b/fuzztest/CMakeLists.txt
@@ -568,10 +568,13 @@
   SRCS
     "internal/runtime_test.cc"
   DEPS
+    fuzztest::configuration
     fuzztest::domain_core
+    fuzztest::flag_name
     fuzztest::runtime
     test_protobuf
     absl::time
+    absl::flags
     GTest::gmock_main
 )
 
diff --git a/fuzztest/init_fuzztest.cc b/fuzztest/init_fuzztest.cc
index e1c56a6..9a71289 100644
--- a/fuzztest/init_fuzztest.cc
+++ b/fuzztest/init_fuzztest.cc
@@ -27,7 +27,6 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/str_join.h"
-#include "absl/strings/str_replace.h"
 #include "absl/strings/string_view.h"
 #include "absl/strings/strip.h"
 #include "absl/time/time.h"
diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc
index 029b7be..6b3b491 100644
--- a/fuzztest/internal/runtime.cc
+++ b/fuzztest/internal/runtime.cc
@@ -46,9 +46,11 @@
 #include "absl/strings/numbers.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
+#include "absl/strings/str_join.h"
 #include "absl/strings/str_replace.h"
 #include "absl/strings/str_split.h"
 #include "absl/strings/string_view.h"
+#include "absl/strings/strip.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
 #include "absl/types/span.h"
@@ -93,23 +95,47 @@
 }
 
 // Returns a reproduction command for replaying
-// `configuration.crashing_input_to_reproduce` from a command line, using the
-// `configuration.reproduction_command_template`.
-std::optional<std::string> GetReproductionCommand(
-    const Configuration& configuration) {
-  if (!configuration.reproduction_command_template.has_value()) {
-    return std::nullopt;
+// `configuration.crashing_input_to_reproduce` or `reproducer_path` from a
+// command line, using the `configuration.reproduction_command_template`.
+std::string GetReproductionCommand(const Configuration* configuration,
+                                   absl::string_view reproducer_path,
+                                   absl::string_view test_name) {
+  const bool is_reproducer_in_corpus_db =
+      configuration && configuration->crashing_input_to_reproduce;
+  CHECK(!reproducer_path.empty() || is_reproducer_in_corpus_db);
+  if (!configuration || !configuration->reproduction_command_template) {
+    absl::string_view reproducer =
+        is_reproducer_in_corpus_db ? *configuration->crashing_input_to_reproduce
+                                   : reproducer_path;
+    return absl::StrFormat(
+        "Replay by adding:\n\n"
+        "--test_filter=%s "
+        "--test_env=FUZZTEST_REPLAY=%s\n\n"
+        "after `bazel test` in your original invocation.\n",
+        test_name, reproducer);
   }
-  if (!configuration.crashing_input_to_reproduce.has_value()) {
-    return std::nullopt;
+  const std::string command_template =
+      *configuration->reproduction_command_template;
+  CHECK(absl::StrContains(command_template, kTestFilterPlaceholder));
+  CHECK(absl::StrContains(command_template, kExtraArgsPlaceholder));
+  if (is_reproducer_in_corpus_db) {
+    const std::string corpus_db = configuration->corpus_database;
+    std::vector<std::string> extra_args = {absl::StrCat(
+        "--test_arg=--", FUZZTEST_FLAG_PREFIX, "corpus_database=", corpus_db)};
+    return absl::StrReplaceAll(
+        command_template,
+        {{kTestFilterPlaceholder,
+          GetFilterForCrashingInput(
+              *configuration->crashing_input_to_reproduce)},
+         {kExtraArgsPlaceholder, absl::StrJoin(extra_args, " ")}});
+  } else {
+    return absl::StrReplaceAll(
+        command_template,
+        {{kTestFilterPlaceholder, test_name},
+         {kExtraArgsPlaceholder,
+          absl::StrCat("--test_env=FUZZTEST_REPLAY=", reproducer_path,
+                       " --test_strategy=local --test_output=streamed")}});
   }
-  CHECK(absl::StrContains(*configuration.reproduction_command_template,
-                          kTestFilterPlaceholder));
-  return absl::StrReplaceAll(
-      *configuration.reproduction_command_template,
-      {{kTestFilterPlaceholder,
-        GetFilterForCrashingInput(
-            *configuration.crashing_input_to_reproduce)}});
 }
 
 struct ReproducerDirectory {
@@ -135,8 +161,8 @@
 }
 
 void PrintReproductionInstructionsForUndeclaredOutputs(
-    RawSink out, absl::string_view reproducer_path,
-    absl::string_view test_name) {
+    RawSink out, absl::string_view test_name,
+    absl::string_view reproducer_path) {
   absl::string_view file_name = Basename(reproducer_path);
   absl::Format(out,
                "Reproducer file was dumped under"
@@ -146,12 +172,49 @@
                "mkdir -p /tmp/%s && \\\ncp -f %s /tmp/%s/%s\n\n",
                kReproducerDirName, reproducer_path, kReproducerDirName,
                file_name);
-  absl::Format(out,
-               "Then replay by adding:\n\n"
-               "--test_filter=%s "
-               "--test_env=FUZZTEST_REPLAY=/tmp/%s/%s\n\n"
-               "after `bazel test` in your original Bazel invocation.\n",
-               test_name, kReproducerDirName, file_name);
+}
+
+absl::string_view GetSeparator() {
+  return "\n================================================================="
+         "\n";
+}
+
+void PrintReproducerIfRequested(RawSink out, const FuzzTest& test,
+                                const Configuration* configuration,
+                                std::string reproducer_path) {
+  const bool is_reproducer_in_corpus_db =
+      configuration && configuration->crashing_input_to_reproduce;
+  if (!is_reproducer_in_corpus_db) {
+    if (reproducer_path.empty()) {
+      absl::FPrintF(GetStderr(), "[!] Failed to write reproducer file!\n");
+      return;
+    }
+  }
+  if (configuration && configuration->reproduction_command_template) {
+    absl::Format(out, "%s=== Reproduction command\n\n", GetSeparator());
+  } else {
+    absl::Format(out, "%s=== Reproducer\n\n", GetSeparator());
+  }
+  const std::string test_name =
+      absl::StrCat(test.suite_name(), ".", test.test_name());
+  if (!is_reproducer_in_corpus_db) {
+    auto out_dir = GetReproducerDirectory();
+    switch (out_dir->type) {
+      case ReproducerDirectory::Type::kUserSpecified:
+        absl::Format(out, "Reproducer file was dumped at:\n%s\n",
+                     reproducer_path);
+        break;
+      case ReproducerDirectory::Type::kTestUndeclaredOutputs:
+        PrintReproductionInstructionsForUndeclaredOutputs(out, test_name,
+                                                          reproducer_path);
+        reproducer_path = absl::StrCat("/tmp/", kReproducerDirName, "/",
+                                       Basename(reproducer_path));
+        break;
+    }
+  }
+  absl::Format(
+      out, "%s\n\n",
+      GetReproductionCommand(configuration, reproducer_path, test_name));
 }
 
 }  // namespace
@@ -179,8 +242,7 @@
 }
 
 void Runtime::PrintFinalStats(RawSink out) const {
-  const std::string separator = '\n' + std::string(65, '=') + '\n';
-  absl::Format(out, "%s=== Fuzzing stats\n\n", separator);
+  absl::Format(out, "%s=== Fuzzing stats\n\n", GetSeparator());
 
   const absl::Duration fuzzing_time = clock_fn_() - stats_->start_time;
   absl::Format(out, "Elapsed time: %s\n", absl::FormatDuration(fuzzing_time));
@@ -214,10 +276,8 @@
     PrintFinalStats(out);
   }
 
-  const std::string separator = '\n' + std::string(65, '=') + '\n';
-
   if (current_args_ != nullptr) {
-    absl::Format(out, "%s=== BUG FOUND!\n\n", separator);
+    absl::Format(out, "%s=== BUG FOUND!\n\n", GetSeparator());
     absl::Format(out, "%s:%d: Counterexample found for %s.%s.\n",
                  current_test_->file(), current_test_->line(),
                  current_test_->suite_name(), current_test_->test_name());
@@ -233,33 +293,10 @@
                        trim ? kTrimIndicator : "");
         });
 
-    // Dump the reproducer if requested.
-    std::optional<ReproducerDirectory> out_dir = GetReproducerDirectory();
-    if (out_dir.has_value()) {
-      absl::Format(out, "%s=== Reproduction\n\n", separator);
-      const std::string reproducer_path = DumpReproducer(out_dir->path);
-      if (reproducer_path.empty()) {
-        absl::FPrintF(GetStderr(), "[!] Failed to write reproducer file!\n");
-      } else {
-        switch (out_dir->type) {
-          case ReproducerDirectory::Type::kUserSpecified:
-            absl::Format(out, "Reproducer file was dumped at:\n%s\n",
-                         reproducer_path);
-            break;
-          case ReproducerDirectory::Type::kTestUndeclaredOutputs:
-            std::string test_name = absl::StrCat(
-                current_test_->suite_name(), ".", current_test_->test_name());
-            PrintReproductionInstructionsForUndeclaredOutputs(
-                out, reproducer_path, test_name);
-            break;
-        }
-      }
-    }
-
     // There doesn't seem to be a good way to generate a reproducer test when
     // the test uses a fixture (see b/241271658).
     if (!current_test_->uses_fixture()) {
-      absl::Format(out, "%s=== Regression test draft\n\n", separator);
+      absl::Format(out, "%s=== Regression test draft\n\n", GetSeparator());
 
       absl::Format(out, "TEST(%1$s, %2$sRegression) {\n  %2$s(\n",
                    current_test_->suite_name(), current_test_->test_name());
@@ -285,16 +322,13 @@
                    "For reproducing findings please rely on file based "
                    "reproduction.\n");
     }
-    if (current_configuration_ != nullptr) {
-      const auto reproduction_command =
-          GetReproductionCommand(*current_configuration_);
-      if (reproduction_command.has_value()) {
-        absl::Format(out, "%s=== Reproduction command\n\n", separator);
-        absl::Format(out, "%s\n\n", *reproduction_command);
-      }
-    }
+    std::optional<ReproducerDirectory> out_dir = GetReproducerDirectory();
+    const std::string reproducer_path =
+        out_dir.has_value() ? DumpReproducer(out_dir->path) : "";
+    PrintReproducerIfRequested(out, *current_test_, current_configuration_,
+                               reproducer_path);
   } else {
-    absl::Format(out, "%s=== SETUP FAILURE!\n\n", separator);
+    absl::Format(out, "%s=== SETUP FAILURE!\n\n", GetSeparator());
     absl::Format(out, "%s:%d: There was a problem with %s.%s.",
                  current_test_->file(), current_test_->line(),
                  current_test_->suite_name(), current_test_->test_name());
@@ -302,7 +336,7 @@
       absl::Format(out, "%s", *test_abort_message);
     }
   }
-  absl::Format(out, "%s", separator);
+  absl::Format(out, "%s", GetSeparator());
 }
 
 void Runtime::StartWatchdog() {
diff --git a/fuzztest/internal/runtime.h b/fuzztest/internal/runtime.h
index 83646a7..7f91945 100644
--- a/fuzztest/internal/runtime.h
+++ b/fuzztest/internal/runtime.h
@@ -390,10 +390,11 @@
 
 size_t GetStackLimitFromEnvOrConfiguration(const Configuration& configuration);
 
-// A reproduction command template will include this placeholder. This
-// placeholder then will be replaced by the proper test filter when creating the
-// final reproduction command from the template.
+// A reproduction command template will include these placeholders. These
+// placeholders then will be replaced by the proper test filter when creating
+// the final reproduction command from the template.
 static constexpr absl::string_view kTestFilterPlaceholder = "$TEST_FILTER";
+static constexpr absl::string_view kExtraArgsPlaceholder = "$EXTRA_ARGS";
 
 }  // namespace internal
 }  // namespace fuzztest
diff --git a/fuzztest/internal/runtime_test.cc b/fuzztest/internal/runtime_test.cc
index 76e072b..e9ae599 100644
--- a/fuzztest/internal/runtime_test.cc
+++ b/fuzztest/internal/runtime_test.cc
@@ -69,7 +69,7 @@
 argument 1: "ABC"
 )"));
 
-  EXPECT_TRUE(absl::EndsWith(report, R"(
+  EXPECT_TRUE(absl::StrContains(report, R"(
 =================================================================
 === Regression test draft