No public description PiperOrigin-RevId: 917997132
diff --git a/centipede/BUILD b/centipede/BUILD index 518d7a6..169c28d 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -31,6 +31,11 @@ licenses(["notice"]) +exports_files([ + "centipede_main.cc", + "seed_corpus_maker.cc", +]) + ################################################################################ # Binaries ################################################################################ @@ -38,6 +43,7 @@ cc_binary( name = "centipede", srcs = ["centipede_main.cc"], + compatible_with = [], deps = [ ":centipede_callbacks", ":centipede_default_callbacks", @@ -46,18 +52,21 @@ ":environment_flags", ":stop", "@abseil-cpp//absl/base:nullability", + "@abseil-cpp//absl/log:flags", ], ) cc_uninstrumented_binary( name = "centipede_uninstrumented", binary = ":centipede", + compatible_with = [], ) # A standalone seed corpus generator. cc_binary( name = "seed_corpus_maker", srcs = ["seed_corpus_maker.cc"], + compatible_with = [], deps = [ ":config_init", ":seed_corpus_maker_flags", @@ -73,6 +82,7 @@ cc_binary( name = "blob_file_converter", srcs = ["blob_file_converter.cc"], + compatible_with = [], deps = [ ":config_init", ":rusage_profiler",
diff --git a/centipede/seed_corpus_maker.cc b/centipede/seed_corpus_maker.cc index 01830d8..2724988 100644 --- a/centipede/seed_corpus_maker.cc +++ b/centipede/seed_corpus_maker.cc
@@ -26,7 +26,7 @@ #include "./common/remote_file.h" int main(int argc, char** absl_nonnull argv) { - (void)fuzztest::internal::InitRuntime(argc, argv); + const auto runtime_state = fuzztest::internal::InitRuntime(argc, argv); const std::string config = absl::GetFlag(FLAGS_config); FUZZTEST_QCHECK(!config.empty());
diff --git a/common/BUILD b/common/BUILD index d64c480..2c0e084 100644 --- a/common/BUILD +++ b/common/BUILD
@@ -14,8 +14,8 @@ # The package contains libraries that are common to both FuzzTest and Centipede. -load("@rules_cc//cc:cc_library.bzl", "cc_library") load("@rules_cc//cc:cc_test.bzl", "cc_test") +load("//third_party/googlefuzztest/build_defs:cc_library.bzl", "cc_library") DEFAULT_VISIBILITY = ["//visibility:public"] @@ -99,6 +99,16 @@ ) cc_library( + name = "env_util", + srcs = ["env_util.cc"], + hdrs = ["env_util.h"], + deps = [ + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/time", + ], +) + +cc_library( name = "hash", srcs = ["hash.cc"], hdrs = ["hash.h"], @@ -126,23 +136,21 @@ "//conditions:default": [], }), deps = [ - ":defs", - ":logging", - ":status_macros", - "@abseil-cpp//absl/base:nullability", - "@abseil-cpp//absl/status", - "@abseil-cpp//absl/status:statusor", - "@abseil-cpp//absl/strings", - ] + select({ - "//conditions:default": [":remote_file_oss"], - }) + - select({ - "@com_google_fuzztest//fuzztest:disable_riegeli": [], - "//conditions:default": [ - "@com_google_riegeli//riegeli/bytes:reader", - "@com_google_riegeli//riegeli/bytes:writer", - ], - }), + ":defs", + ":logging", + ":remote_file_oss", + ":status_macros", + "@abseil-cpp//absl/base:nullability", + "@abseil-cpp//absl/status", + "@abseil-cpp//absl/status:statusor", + "@abseil-cpp//absl/strings", + ] + select({ + "@com_google_fuzztest//fuzztest:disable_riegeli": [], + "//conditions:default": [ + "@com_google_riegeli//riegeli/bytes:reader", + "@com_google_riegeli//riegeli/bytes:writer", + ], + }), ) cc_library( @@ -155,7 +163,6 @@ "@com_google_fuzztest//fuzztest:disable_riegeli": ["CENTIPEDE_DISABLE_RIEGELI"], "//conditions:default": [], }), - visibility = ["//visibility:private"], deps = [ ":defs", ":logging", @@ -252,14 +259,9 @@ ], ) -# TODO(b/324462306): Merge this with remote_file_test once the bug is fixed. -cc_library( - name = "remote_file_test_lib", - testonly = True, +cc_test( + name = "remote_file_test", srcs = ["remote_file_test.cc"], - defines = select({ - "//conditions:default": [], - }), deps = [ ":logging", ":remote_file", @@ -267,16 +269,6 @@ "@abseil-cpp//absl/status", "@abseil-cpp//absl/time", "@googletest//:gtest", - ] + select({ - "//conditions:default": [], - }), - alwayslink = True, -) - -cc_test( - name = "remote_file_test", - deps = [ - ":remote_file_test_lib", "@googletest//:gtest_main", ], )
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f451674..aaf302e 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt
@@ -84,6 +84,19 @@ fuzztest_cc_library( NAME + env_util + HDRS + "env_util.h" + SRCS + "env_util.cc" + DEPS + absl::strings + absl::str_format + absl::time +) + +fuzztest_cc_library( + NAME hash HDRS "hash.h"
diff --git a/common/env_util.cc b/common/env_util.cc new file mode 100644 index 0000000..5445ee7 --- /dev/null +++ b/common/env_util.cc
@@ -0,0 +1,41 @@ +// Copyright 2026 The Centipede Authors. +// +// 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 "./common/env_util.h" + +#include <cstdlib> +#include <string> + +#include "absl/strings/str_format.h" +#include "absl/time/time.h" + +namespace fuzztest::internal { + +absl::Duration GetDurationFromEnv(const char* env_var_name, + absl::Duration default_value) { + const char* env_val = std::getenv(env_var_name); + if (env_val == nullptr) return default_value; + + absl::Duration duration; + if (absl::ParseDuration(env_val, &duration)) { + return duration; + } + + absl::FPrintF( + stderr, "[!] Cannot parse env %s=%s as duration. Using %v as default.\n", + env_var_name, env_val, default_value); + return default_value; +} + +} // namespace fuzztest::internal
diff --git a/common/env_util.h b/common/env_util.h new file mode 100644 index 0000000..f61110f --- /dev/null +++ b/common/env_util.h
@@ -0,0 +1,29 @@ +// Copyright 2026 The Centipede Authors. +// +// 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_COMMON_ENV_UTIL_H_ +#define FUZZTEST_COMMON_ENV_UTIL_H_ + +#include "absl/time/time.h" + +namespace fuzztest::internal { + +// Returns the duration parsed from the environment variable `env_var_name`. +// If the variable is unset or cannot be parsed, returns `default_value`. +absl::Duration GetDurationFromEnv(const char* env_var_name, + absl::Duration default_value); + +} // namespace fuzztest::internal + +#endif // FUZZTEST_COMMON_ENV_UTIL_H_
diff --git a/common/remote_file.h b/common/remote_file.h index 0b70444..bde7fb9 100644 --- a/common/remote_file.h +++ b/common/remote_file.h
@@ -135,8 +135,8 @@ // Lists all files within `path`, recursively expanding subdirectories if // `recursively` is true. Does not return any directories. Returns an empty -// vector if `path` is an empty directory, or `path` does not exist. Returns -// `{path}` if `path` is a non-directory. +// vector if `path` is an empty directory, `path` does not exist, or `path` +// is a non-directory. absl::StatusOr<std::vector<std::string>> RemoteListFiles(std::string_view path, bool recursively);
diff --git a/common/remote_file_oss.cc b/common/remote_file_oss.cc index 10299f6..d69b3b8 100644 --- a/common/remote_file_oss.cc +++ b/common/remote_file_oss.cc
@@ -223,6 +223,7 @@ absl::StatusOr<std::vector<std::string>> RemoteListFiles(std::string_view path, bool recursively) { if (!std::filesystem::exists(path)) return std::vector<std::string>(); + if (!std::filesystem::is_directory(path)) return std::vector<std::string>(); auto list_files = [](auto dir_iter) { std::vector<std::string> ret; for (const auto &entry : dir_iter) {
diff --git a/common/remote_file_test.cc b/common/remote_file_test.cc index 82fd85b..250f223 100644 --- a/common/remote_file_test.cc +++ b/common/remote_file_test.cc
@@ -114,18 +114,6 @@ EXPECT_THAT(*files, IsEmpty()); } -TEST(RemoteListFiles, ReturnsASingleFileWhenListingAFile) { - const fs::path temp_dir = GetTestTempDir(test_info_->name()); - - const std::string file1_path = temp_dir / "file_01"; - CreateFileOrDie(file1_path); - - const absl::StatusOr<std::vector<std::string>> files = - RemoteListFiles(temp_dir.string(), /*recursively=*/false); - ASSERT_TRUE(files.status().ok()); - EXPECT_THAT(*files, UnorderedElementsAre(file1_path)); -} - TEST(RemoteListFiles, ReturnsAnEmptyVectorWhenPathDoesNotExist) { const absl::StatusOr<std::vector<std::string>> files = RemoteListFiles("/this/file/path/does/not/exist", /*recursively=*/false); @@ -133,6 +121,16 @@ EXPECT_THAT(*files, IsEmpty()); } +TEST(RemoteListFiles, ReturnsAnEmptyVectorWhenPathIsNonDirectory) { + const fs::path temp_dir = GetTestTempDir(test_info_->name()); + const std::string file_path = temp_dir / "file"; + CreateFileOrDie(file_path); + const absl::StatusOr<std::vector<std::string>> files = + RemoteListFiles(file_path, /*recursively=*/false); + ASSERT_TRUE(files.status().ok()); + EXPECT_THAT(*files, IsEmpty()); +} + TEST(RemotePathDelete, RecursivelyDeletesAllFilesAndSubdirectories) { const fs::path temp_dir = GetTestTempDir(test_info_->name()); const fs::path a_b_c = temp_dir / "a" / "b" / "c";
diff --git a/e2e_tests/BUILD b/e2e_tests/BUILD index fc0e23f..63621bc 100644 --- a/e2e_tests/BUILD +++ b/e2e_tests/BUILD
@@ -63,6 +63,7 @@ ], deps = [ ":test_binary_util", + "@abseil-cpp//absl/base:config", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/strings", "@abseil-cpp//absl/strings:str_format", @@ -147,6 +148,7 @@ ], deps = [ ":test_binary_util", + "@abseil-cpp//absl/base:config", "@abseil-cpp//absl/base:no_destructor", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/status",
diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc index 4ce64b9..69ca49e 100644 --- a/e2e_tests/corpus_database_test.cc +++ b/e2e_tests/corpus_database_test.cc
@@ -20,6 +20,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/no_destructor.h" #include "absl/container/flat_hash_map.h" #include "absl/status/status.h" @@ -56,7 +57,7 @@ absl::StatusOr<std::string> FindFile(absl::string_view root_path, absl::string_view file_name) { - for (const std::string &path : ListDirectoryRecursively(root_path)) { + for (const std::string& path : ListDirectoryRecursively(root_path)) { if (std::filesystem::path(path).filename() == file_name) return path; } return absl::NotFoundError(absl::StrCat("File ", file_name, " not found.")); @@ -91,12 +92,17 @@ static void RunUpdateCorpusDatabase() { if (run_map_->contains(GetParam())) return; - auto &run = (*run_map_)[GetParam()]; + auto& run = (*run_map_)[GetParam()]; run.workspace = std::make_unique<TempDir>(); RunOptions run_options; + std::string fuzz_for = "30s"; +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) + fuzz_for = "90s"; +#endif run_options.fuzztest_flags = { {"corpus_database", GetCorpusDatabasePath()}, - {"fuzz_for", "30s"}, + {"fuzz_for", fuzz_for}, {"jobs", "2"}, }; auto [status_unused, std_out_unused, std_err] = RunBinaryMaybeWithCentipede(
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc index 6133473..76270b7 100644 --- a/e2e_tests/functional_test.cc +++ b/e2e_tests/functional_test.cc
@@ -26,6 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/flat_hash_map.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -58,6 +59,15 @@ namespace fuzztest::internal { namespace { +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) +constexpr const char* kShortFuzzFor = "30s"; +constexpr absl::Duration kShortTimeout = absl::Seconds(90); +#else +constexpr const char* kShortFuzzFor = "10s"; +constexpr absl::Duration kShortTimeout = absl::Seconds(30); +#endif + using ::fuzztest::domain_implementor::PrintMode; using ::testing::_; using ::testing::AllOf; @@ -139,7 +149,7 @@ fuzzer_flags["print_subprocess_log"] = "true"; fuzzer_flags["unguided"] = "true"; if (!fuzzer_flags.contains("fuzz_for")) { - fuzzer_flags["fuzz_for"] = "10s"; + fuzzer_flags["fuzz_for"] = "60s"; } return RunWithExactFuzzerFlags(test_filter, target_binary, env, std::move(fuzzer_flags)); @@ -533,7 +543,9 @@ } TEST_F(UnitTestModeTest, DetectsRecursiveStructureIfOptionalsSetByDefault) { - auto [status, std_out, std_err] = Run("MySuite.FailsIfCantInitializeProto"); + auto [status, std_out, std_err] = + Run("MySuite.FailsIfCantInitializeProto", kDefaultTargetBinary, + /*env=*/{}, {{"fuzz_for", "60s"}}); ExpectTargetAbort(status, std_err); EXPECT_THAT_LOG(std_err, HasSubstr("recursive fields")); } @@ -828,9 +840,9 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, IgnoresNegativeFuzzingRunsLimitInEnvVar) { auto [status, std_out, std_err] = - RunWith({{"fuzz", "MySuite.PassesWithPositiveInput"}}, + RunWith({{"fuzz", "MySuite.PassesWithPositiveInput"}, {"fuzz_for", "1s"}}, {{"FUZZTEST_MAX_FUZZING_RUNS", "-1"}}, - /*timeout=*/absl::Seconds(10)); + /*timeout=*/absl::Seconds(60)); EXPECT_THAT_LOG(std_err, HasSubstr("will not limit fuzzing runs")); } @@ -953,7 +965,7 @@ // Although theoretically possible, it is extreme unlikely that the test would // find the crash without saving some corpus. auto [producer_status, producer_std_out, producer_std_err] = - RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "10s"}}, + RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "60s"}}, {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}}); auto corpus_files = ReadFileOrDirectory(corpus_dir.path().c_str()); @@ -972,7 +984,7 @@ // Although theoretically possible, it is extreme unlikely that the test would // find the crash without saving some corpus. auto [producer_status, producer_std_out, producer_std_err] = - RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "10s"}}, + RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "60s"}}, {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}}); auto corpus_files = ReadFileOrDirectory(corpus_dir.path().c_str()); @@ -1012,7 +1024,7 @@ // Although theoretically possible, it is extreme unlikely that the test would // find the crash without saving some corpus. auto [producer_status, producer_std_out, producer_std_err] = - RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "10s"}}, + RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "60s"}}, {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}}); auto corpus_files = ReadFileOrDirectory(corpus_dir.path().c_str()); @@ -1212,10 +1224,11 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, DoesNotPrintWarningForDisabledLimitFlagsByDefault) { - auto [status, std_out, std_err] = RunWith( - {{"fuzz", "MySuite.PassesWithPositiveInput"}, {"fuzz_for", "10s"}}, - /*env=*/{}, - /*timeout=*/absl::Seconds(20)); + auto [status, std_out, std_err] = + RunWith({{"fuzz", "MySuite.PassesWithPositiveInput"}, + {"fuzz_for", kShortFuzzFor}}, + /*env=*/{}, + /*timeout=*/absl::Seconds(80)); EXPECT_THAT_LOG(std_err, Not(HasSubstr("limit is specified but will be ignored"))); EXPECT_THAT(status, Eq(ExitCode(0))); @@ -1224,7 +1237,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, RssLimitFlagWorks) { auto [status, std_out, std_err] = RunWith( {{"fuzz", "MySuite.LargeHeapAllocation"}, {"rss_limit_mb", "1024"}}, - /*env=*/{}, /*timeout=*/absl::Seconds(10)); + /*env=*/{}, /*timeout=*/absl::Seconds(60)); EXPECT_THAT_LOG(std_err, HasSubstr("argument 0: ")); EXPECT_THAT_LOG(std_err, ContainsRegex(absl::StrCat("RSS limit exceeded"))); ExpectTargetAbort(status, std_err); @@ -1243,7 +1256,7 @@ // to restrict the filter to only fuzz tests. TEST_F(FuzzingModeCommandLineInterfaceTest, RunsOnlyFuzzTests) { auto [status, std_out, std_err] = - RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10), + RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(60), "testdata/unit_test_and_fuzz_tests"); EXPECT_THAT_LOG(std_out, @@ -1257,7 +1270,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, AllowsSpecifyingFilterWithFuzzForDuration) { auto [status, std_out, std_err] = - RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10), + RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(60), "testdata/unit_test_and_fuzz_tests", {{GTEST_FLAG_PREFIX_ "filter", "UnitTest.AlwaysPasses:FuzzTest.AlwaysPasses"}}); @@ -1278,7 +1291,7 @@ // Although theoretically possible, it is extreme unlikely that the test would // find the crash without saving some corpus. auto [producer_status, producer_std_out, producer_std_err] = - RunWith({{"fuzz", "MySuite.SkipInputs"}, {"fuzz_for", "10s"}}, + RunWith({{"fuzz", "MySuite.SkipInputs"}, {"fuzz_for", kShortFuzzFor}}, {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}}); ASSERT_THAT_LOG(producer_std_err, HasSubstr("Skipped input")); @@ -1338,10 +1351,10 @@ #endif const auto [status_unused, std_out, std_err] = RunWith( { - {"fuzz_for", "10s"}, + {"fuzz_for", "30s"}, {"fuzz", "FaultySetupTest.NoOp"}, }, - {}, absl::Seconds(10), kDefaultTargetBinary); + {}, absl::Seconds(60), kDefaultTargetBinary); EXPECT_THAT_LOG(std_out, HasSubstr("[ FAILED ] FaultySetupTest.NoOp")) << "\nstd_err:\n" << std_err; @@ -1356,11 +1369,11 @@ const auto [status_unused, std_out, std_err] = RunWith( { {"corpus_database", corpus_database.path()}, - {"fuzz_for", "10s"}, + {"fuzz_for", "30s"}, {"fuzz", "FaultySetupTest.NoOp"}, {"execution_id", "some_execution_id"}, }, - {}, absl::Seconds(10), kDefaultTargetBinary); + {}, absl::Seconds(60), kDefaultTargetBinary); EXPECT_THAT_LOG(std_out, HasSubstr("[ FAILED ] FaultySetupTest.NoOp")) << "\nstd_err:\n" << std_err; @@ -1854,7 +1867,7 @@ TEST_P(FuzzingModeCrashFindingTest, FlatMapPassesWhenCorrect) { auto [status, std_out, std_err] = Run("MySuite.FlatMapPassesWhenCorrect", kDefaultTargetBinary, - /*env=*/{}, /*timeout=*/absl::Seconds(10)); + /*env=*/{}, /*timeout=*/absl::Seconds(60)); EXPECT_THAT(status, Eq(ExitCode(0))); } @@ -2075,7 +2088,7 @@ /*env=*/ { }, - /*timeout=*/absl::Seconds(30)); + /*timeout=*/absl::Seconds(120)); EXPECT_THAT_LOG(std_err, HasSubstr("argument 0: \"ahmfn\"")); ExpectTargetAbort(status, std_err); }
diff --git a/e2e_tests/testdata/BUILD b/e2e_tests/testdata/BUILD index 0db3a80..77ee7a1 100644 --- a/e2e_tests/testdata/BUILD +++ b/e2e_tests/testdata/BUILD
@@ -93,6 +93,7 @@ "@abseil-cpp//absl/strings:str_format", "@abseil-cpp//absl/time", "@abseil-cpp//absl/types:span", + "@com_google_fuzztest//common:env_util", "@com_google_fuzztest//common:logging", "@com_google_fuzztest//fuzztest", "@com_google_fuzztest//fuzztest:flatbuffers",
diff --git a/e2e_tests/testdata/CMakeLists.txt b/e2e_tests/testdata/CMakeLists.txt index ec93a80..b3ff52d 100644 --- a/e2e_tests/testdata/CMakeLists.txt +++ b/e2e_tests/testdata/CMakeLists.txt
@@ -43,6 +43,7 @@ fuzztest_flatbuffers fuzztest_googletest_fixture_adapter fuzztest_common_logging + fuzztest_env_util fuzztest_test_flatbuffers_headers ) link_fuzztest(fuzz_tests_for_functional_testing.stripped)
diff --git a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc index 0a066d0..f577b37 100644 --- a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc +++ b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
@@ -38,6 +38,7 @@ #include "absl/strings/string_view.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#include "./common/env_util.h" #include "./common/logging.h" #include "./fuzztest/internal/test_flatbuffers_generated.h" #include "./fuzztest/internal/test_protobuf.pb.h" @@ -819,7 +820,11 @@ static bool first_input = true; if (first_input) { first_input = false; - absl::SleepFor(absl::Seconds(2)); + absl::Duration sleep_duration = fuzztest::internal::GetDurationFromEnv( + "FUZZTEST_FIRST_INPUT_SLEEP_DURATION", absl::Seconds(2)); + if (sleep_duration > absl::ZeroDuration()) { + absl::SleepFor(sleep_duration); + } } } }
diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index 2bf470f..6181c55 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD
@@ -103,6 +103,7 @@ "@com_google_fuzztest//centipede:stop", "@com_google_fuzztest//centipede:workdir", "@com_google_fuzztest//common:defs", + "@com_google_fuzztest//common:env_util", "@com_google_fuzztest//common:logging", "@com_google_fuzztest//common:remote_file", "@com_google_fuzztest//common:temp_dir", @@ -264,6 +265,7 @@ "@abseil-cpp//absl/functional:function_ref", "@abseil-cpp//absl/hash", "@abseil-cpp//absl/status", + "@abseil-cpp//absl/strings", "@abseil-cpp//absl/strings:str_format", "@abseil-cpp//absl/strings:string_view", "@abseil-cpp//absl/time", @@ -272,9 +274,7 @@ "@com_google_fuzztest//common:defs", "@com_google_fuzztest//common:logging", "@com_google_fuzztest//common:remote_file", - ] + select({ - "//conditions:default": [], - }), + ], ) cc_test(
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc index 276a331..f2c904d 100644 --- a/fuzztest/internal/centipede_adaptor.cc +++ b/fuzztest/internal/centipede_adaptor.cc
@@ -75,6 +75,7 @@ #include "./centipede/stop.h" #include "./centipede/workdir.h" #include "./common/defs.h" +#include "./common/env_util.h" #include "./common/logging.h" #include "./common/remote_file.h" #include "./common/temp_dir.h" @@ -219,7 +220,8 @@ const Configuration& configuration, absl::string_view workdir, absl::string_view test_name, RunMode run_mode) { fuzztest::internal::Environment env = CreateDefaultCentipedeEnvironment(); - constexpr absl::Duration kUnitTestDefaultDuration = absl::Seconds(3); + absl::Duration unit_test_default_duration = GetDurationFromEnv( + "FUZZTEST_UNIT_TEST_DEFAULT_DURATION", absl::Seconds(3)); env.fuzztest_multi_test_mode_soon_to_be_removed = false; if (configuration.time_limit_per_input < absl::InfiniteDuration()) { const int64_t time_limit_seconds = @@ -261,7 +263,7 @@ // duration as the special value. if (total_time_limit == absl::ZeroDuration() && run_mode == RunMode::kUnitTest) { - total_time_limit = kUnitTestDefaultDuration; + total_time_limit = unit_test_default_duration; } { Configuration single_test_configuration = configuration;
diff --git a/fuzztest/internal/io.cc b/fuzztest/internal/io.cc index d9e1ef2..5b173ca 100644 --- a/fuzztest/internal/io.cc +++ b/fuzztest/internal/io.cc
@@ -14,13 +14,8 @@ #include "./fuzztest/internal/io.h" -#include <cerrno> -#include <cstring> -#include <filesystem> // NOLINT -#include <fstream> #include <memory> #include <optional> -#include <sstream> #include <string> #include <string_view> #include <tuple> @@ -30,6 +25,7 @@ #include "absl/functional/function_ref.h" #include "absl/hash/hash.h" #include "absl/status/status.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/time/clock.h" @@ -43,118 +39,62 @@ namespace fuzztest::internal { -#if defined(FUZZTEST_STUB_STD_FILESYSTEM) - -// TODO(lszekeres): Return absl::Status instead of bool for these. +absl::string_view Dirname(absl::string_view filename) { + auto last_slash_pos = filename.find_last_of("/\\"); + if (last_slash_pos == absl::string_view::npos) return ""; + if (last_slash_pos == 0) return filename.substr(0, 1); + return filename.substr(0, last_slash_pos); +} bool WriteFile(absl::string_view path, absl::string_view contents) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -std::optional<std::string> ReadFile(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -bool IsDirectory(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -bool CreateDirectory(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -std::vector<std::string> ListDirectory(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -std::vector<std::string> ListDirectoryRecursively(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -#else - -bool WriteFile(absl::string_view path, absl::string_view contents) { - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; - - // Just in case the directory does not currently exist. - // If it does, this is a noop. - CreateDirectory(fs_path.parent_path().string()); - - std::ofstream file(fs_path); - file << contents; - file.close(); - if (!file.good()) { - absl::FPrintF(GetStderr(), "[!] %s:%d: Error writing %s: (%d) %s\n", - __FILE__, __LINE__, path, errno, strerror(errno)); + auto dirname = Dirname(path); + if (!dirname.empty() && dirname != "/") { + if (!RemoteMkdir(dirname).ok()) { + absl::FPrintF(GetStderr(), "[!] %s:%d: Couldn't create directory: %s\n", + __FILE__, __LINE__, dirname); + return false; + } } - return !file.fail(); + auto status = RemoteFileSetContents(path, std::string(contents)); + if (!status.ok()) { + absl::FPrintF(GetStderr(), "[!] %s:%d: Error writing %s: %s\n", __FILE__, + __LINE__, path, status.message()); + } + return status.ok(); } std::optional<std::string> ReadFile(absl::string_view path) { - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; - - std::ifstream stream(fs_path); - if (!stream.good()) { - // Using stderr instead of GetStderr() to avoid initialization-order-fiasco - // when reading files at static init time with - // `.WithSeeds(fuzztest::ReadFilesFromDirectory(...))`. - absl::FPrintF(stderr, "[!] %s:%d: Error reading %s: (%d) %s\n", __FILE__, - __LINE__, path, errno, strerror(errno)); + std::string contents; + auto status = RemoteFileGetContents(path, contents); + if (!status.ok()) { + absl::FPrintF(stderr, "[!] %s:%d: Error reading %s: %s\n", __FILE__, + __LINE__, path, status.message()); return std::nullopt; } - std::stringstream buffer; - buffer << stream.rdbuf(); - return buffer.str(); + return contents; } -bool IsDirectory(absl::string_view path) { - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; +bool IsDirectory(absl::string_view path) { return RemotePathIsDirectory(path); } - return std::filesystem::is_directory(fs_path); -} - -bool CreateDirectory(absl::string_view path) { - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; - - return std::filesystem::create_directories(fs_path); -} +bool CreateDirectory(absl::string_view path) { return RemoteMkdir(path).ok(); } std::vector<std::string> ListDirectory(absl::string_view path) { - std::vector<std::string> output_paths; - - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; - if (!std::filesystem::is_directory(fs_path)) return output_paths; - for (const auto& entry : std::filesystem::directory_iterator(fs_path)) { - output_paths.push_back(entry.path().string()); - } - return output_paths; + auto files = RemoteListFiles(path, /*recursively=*/false); + if (!files.ok()) return {}; + return *files; } std::vector<std::string> ListDirectoryRecursively(absl::string_view path) { - std::vector<std::string> output_paths; - - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; - for (const auto& entry : std::filesystem::recursive_directory_iterator( - fs_path, - std::filesystem::directory_options::follow_directory_symlink)) { - output_paths.push_back(entry.path().string()); - } - return output_paths; + auto files = RemoteListFiles(path, /*recursively=*/true); + if (!files.ok()) return {}; + return *files; } -#endif // FUZZTEST_STUB_STD_FILESYSTEM - std::string WriteDataToDir(absl::string_view data, absl::string_view outdir) { - std::string filename(outdir); - if (filename.back() != '/') filename += '/'; - absl::StrAppendFormat(&filename, "%016x", - absl::Hash<absl::string_view>{}(data)); + std::string basename = + absl::StrFormat("%016x", absl::Hash<absl::string_view>{}(data)); + const bool need_slash = !outdir.empty() && outdir.back() != '/'; + std::string filename = absl::StrCat(outdir, need_slash ? "/" : "", basename); if (!WriteFile(filename, data)) return ""; return filename; }