Pass through the undeclared outputs dir to the runner for dumping reproducers. Since Centipede blocks the original outputs var to avoid spamming user test outputs, this change save the output path to a new env var only used by FuzzTest. PiperOrigin-RevId: 735594597
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc index 7e8434f..3bd8c91 100644 --- a/e2e_tests/functional_test.cc +++ b/e2e_tests/functional_test.cc
@@ -787,7 +787,8 @@ EXPECT_THAT(std_err, HasSubstr("Fuzzing timeout set to: 1s")) << std_err; } -TEST_F(FuzzingModeCommandLineInterfaceTest, ReproducerIsDumpedWhenEnvVarIsSet) { +TEST_F(FuzzingModeCommandLineInterfaceTest, + ReproducerIsDumpedWhenReproducersOutEnvVarIsSet) { TempDir out_dir; auto [status, std_out, std_err] = @@ -809,6 +810,32 @@ replay_files[0].path)))); } +TEST_F(FuzzingModeCommandLineInterfaceTest, + ReproducerIsDumpedWhenUndeclaredOutputsEnvVarIsSet) { + TempDir out_dir; + + auto [status, std_out, std_err] = + RunWith({{"fuzz", "MySuite.StringFast"}}, + { + {"TEST_UNDECLARED_OUTPUTS_DIR", out_dir.path()}, + }); + EXPECT_THAT(std_err, HasSubstr("argument 0: \"Fuzz")); + ExpectTargetAbort(status, std_err); + + auto replay_files = ReadFileOrDirectory(out_dir.path().c_str()); + ASSERT_EQ(replay_files.size(), 1) << std_err; + auto parsed = IRObject::FromString(replay_files[0].data); + ASSERT_TRUE(parsed) << std_err; + auto args = parsed->ToCorpus<std::tuple<std::string>>(); + EXPECT_THAT(args, Optional(FieldsAre(StartsWith("Fuzz")))) << std_err; + const std::string expected_reproducer_message = + "Reproducer file was dumped under TEST_UNDECLARED_OUTPUTS_DIR"; + EXPECT_THAT(std_err, AllOf(HasSubstr(expected_reproducer_message), + HasSubstr(replay_files[0].path), + HasSubstr("--test_env=FUZZTEST_REPLAY="))) + << std_err; +} + TEST_F(FuzzingModeCommandLineInterfaceTest, SavesCorpusWhenEnvVarIsSet) { TempDir out_dir; // We cannot use a non-crashing test since there is no easy way to limit the
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 7f89758..facc099 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc
@@ -174,6 +174,12 @@ return *cached_self_binary_hash; } +void PropagateTestUndeclaredOutputsDir() { + const char* env = std::getenv("TEST_UNDECLARED_OUTPUTS_DIR"); + if (env == nullptr) return; + setenv("FUZZTEST_INTERNAL_TEST_UNDECLARED_OUTPUTS_DIR", env, /*overwrite=*/1); +} + std::string ShellEscape(absl::string_view str) { return absl::StrCat("'", absl::StrReplaceAll(str, {{"'", "'\\''"}}), "'"); } @@ -695,6 +701,7 @@ corpus_out_dir); return 0; } + if (env.exit_on_crash) PropagateTestUndeclaredOutputsDir(); return centipede::CentipedeMain(env, factory); })(); if (to_tear_down_fuzz_test) {
diff --git a/fuzztest/internal/runtime.cc b/fuzztest/internal/runtime.cc index c402191..4f09546 100644 --- a/fuzztest/internal/runtime.cc +++ b/fuzztest/internal/runtime.cc
@@ -163,6 +163,18 @@ return ReproducerDirectory{ path.string(), ReproducerDirectory::Type::kTestUndeclaredOutputs}; } +#ifdef FUZZTEST_USE_CENTIPEDE + if (getenv("CENTIPEDE_RUNNER_FLAGS") != nullptr) { + env = absl::NullSafeStringView( + getenv("FUZZTEST_INTERNAL_TEST_UNDECLARED_OUTPUTS_DIR")); + if (!env.empty()) { + auto path = std::filesystem::path(std::string(env)) / + std::string(kReproducerDirName); + return ReproducerDirectory{ + path.string(), ReproducerDirectory::Type::kTestUndeclaredOutputs}; + } + } +#endif return std::nullopt; } @@ -171,7 +183,7 @@ absl::string_view reproducer_path) { absl::string_view file_name = Basename(reproducer_path); absl::Format(out, - "Reproducer file was dumped under" + "Reproducer file was dumped under " "TEST_UNDECLARED_OUTPUTS_DIR.\n"); absl::Format(out, "Make a copy of it with:\n\n"