diff --git a/centipede/BUILD b/centipede/BUILD index f16df0407..2ecf2b478 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -38,6 +38,7 @@ licenses(["notice"]) cc_binary( name = "centipede", srcs = ["centipede_main.cc"], + compatible_with = [], deps = [ ":centipede_callbacks", ":centipede_default_callbacks", @@ -52,12 +53,14 @@ cc_binary( 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 +76,7 @@ cc_binary( cc_binary( name = "blob_file_converter", srcs = ["blob_file_converter.cc"], + compatible_with = [], deps = [ ":config_init", ":rusage_profiler", @@ -1992,3 +1996,12 @@ sh_test( ":test_util_sh", ], ) + +filegroup( + name = "oss_srcs", + srcs = [ + "centipede_main.cc", + "seed_corpus_maker.cc", + ], + visibility = ["//visibility:private"], +) diff --git a/common/BUILD b/common/BUILD index d64c48092..1e05a0bf5 100644 --- a/common/BUILD +++ b/common/BUILD @@ -14,7 +14,6 @@ # 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") DEFAULT_VISIBILITY = ["//visibility:public"] @@ -98,6 +97,17 @@ cc_library( deps = ["@abseil-cpp//absl/types:span"], ) +cc_library( + name = "env_util", + srcs = ["env_util.cc"], + hdrs = ["env_util.h"], + deps = [ + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:str_format", + "@abseil-cpp//absl/time", + ], +) + cc_library( name = "hash", srcs = ["hash.cc"], @@ -126,23 +136,21 @@ cc_library( "//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 @@ cc_library( "@com_google_fuzztest//fuzztest:disable_riegeli": ["CENTIPEDE_DISABLE_RIEGELI"], "//conditions:default": [], }), - visibility = ["//visibility:private"], deps = [ ":defs", ":logging", @@ -253,13 +260,11 @@ cc_test( ) # TODO(b/324462306): Merge this with remote_file_test once the bug is fixed. -cc_library( - name = "remote_file_test_lib", - testonly = True, +exports_files(["remote_file_test.cc"]) + +cc_test( + name = "remote_file_test", srcs = ["remote_file_test.cc"], - defines = select({ - "//conditions:default": [], - }), deps = [ ":logging", ":remote_file", @@ -267,16 +272,6 @@ cc_library( "@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 f4516741f..aaf302ece 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -82,6 +82,19 @@ fuzztest_cc_library( absl::span ) +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 diff --git a/common/env_util.cc b/common/env_util.cc new file mode 100644 index 000000000..56b78fb5e --- /dev/null +++ b/common/env_util.cc @@ -0,0 +1,42 @@ +// 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 +#include + +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace fuzztest::internal { + +absl::Duration GetDurationFromEnv(absl::string_view env_var_name, + absl::Duration default_value) { + const char* env_val = std::getenv(std::string(env_var_name).c_str()); + 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 default.\n", + env_var_name, env_val); + return default_value; +} + +} // namespace fuzztest::internal diff --git a/common/env_util.h b/common/env_util.h new file mode 100644 index 000000000..567f66c1a --- /dev/null +++ b/common/env_util.h @@ -0,0 +1,30 @@ +// 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/strings/string_view.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(absl::string_view env_var_name, + absl::Duration default_value); + +} // namespace fuzztest::internal + +#endif // FUZZTEST_COMMON_ENV_UTIL_H_ diff --git a/common/remote_file_oss.cc b/common/remote_file_oss.cc index 10299f6b6..d69b3b87a 100644 --- a/common/remote_file_oss.cc +++ b/common/remote_file_oss.cc @@ -223,6 +223,7 @@ bool RemotePathIsDirectory(std::string_view path) { absl::StatusOr> RemoteListFiles(std::string_view path, bool recursively) { if (!std::filesystem::exists(path)) return std::vector(); + if (!std::filesystem::is_directory(path)) return std::vector(); auto list_files = [](auto dir_iter) { std::vector ret; for (const auto &entry : dir_iter) { diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc index 4ce64b915..737766100 100644 --- a/e2e_tests/corpus_database_test.cc +++ b/e2e_tests/corpus_database_test.cc @@ -94,9 +94,13 @@ class UpdateCorpusDatabaseTest auto &run = (*run_map_)[GetParam()]; run.workspace = std::make_unique(); RunOptions run_options; + std::string fuzz_for = "30s"; +#if defined(__has_feature) && __has_feature(address_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 613347390..2788c1c10 100644 --- a/e2e_tests/functional_test.cc +++ b/e2e_tests/functional_test.cc @@ -58,6 +58,15 @@ namespace fuzztest::internal { namespace { +#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || \ + defined(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 +148,7 @@ class UnitTestModeTest : public ::testing::Test { 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 +542,9 @@ TEST_F( } 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 +839,9 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, 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 +964,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, RestoresCorpusWhenEnvVarIsSet) { // 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 +983,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, MinimizesCorpusWhenEnvVarIsSet) { // 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 +1023,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, MinimizesDuplicatedCorpus) { // 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 +1223,11 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, ConfiguresStackLimitByFlag) { 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 +1236,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, 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 +1255,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, TimeLimitFlagWorks) { // 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 +1269,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, RunsOnlyFuzzTests) { 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 +1290,7 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, CorpusDoesNotContainSkippedInputs) { // 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 +1350,10 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, #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 +1368,11 @@ TEST_F(FuzzingModeCommandLineInterfaceTest, 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 +1866,7 @@ TEST_P(FuzzingModeCrashFindingTest, FlatMappedDomainShowsMappedValue) { 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 +2087,7 @@ TEST_P(FuzzingModeCrashFindingTest, /*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 0db3a8084..77ee7a14e 100644 --- a/e2e_tests/testdata/BUILD +++ b/e2e_tests/testdata/BUILD @@ -93,6 +93,7 @@ cc_binary( "@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 ec93a80aa..b3ff52d8d 100644 --- a/e2e_tests/testdata/CMakeLists.txt +++ b/e2e_tests/testdata/CMakeLists.txt @@ -43,6 +43,7 @@ target_link_libraries( 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 0a066d024..6c8fbbcc5 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 @@ void DetectRegressionAndCoverageInputs(const std::string& input) { static bool first_input = true; if (first_input) { first_input = false; - absl::SleepFor(absl::Seconds(2)); + absl::Duration sleep_duration = fuzztest::internal::GetDurationFromEnv( + "TEST_INPUT_SLEEP", absl::Seconds(2)); + if (sleep_duration > absl::ZeroDuration()) { + absl::SleepFor(sleep_duration); + } } } } diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD index f51559105..ea2ff69a5 100644 --- a/fuzztest/internal/BUILD +++ b/fuzztest/internal/BUILD @@ -103,6 +103,7 @@ cc_library( "@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", @@ -272,9 +273,7 @@ cc_library( "@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 c507eb264..03ead0b32 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 @@ fuzztest::internal::Environment CreateCentipedeEnvironmentFromConfiguration( 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 @@ fuzztest::internal::Environment CreateCentipedeEnvironmentFromConfiguration( // 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 d9e1ef219..9d4e32cb0 100644 --- a/fuzztest/internal/io.cc +++ b/fuzztest/internal/io.cc @@ -14,13 +14,8 @@ #include "./fuzztest/internal/io.h" -#include -#include -#include // NOLINT -#include #include #include -#include #include #include #include @@ -43,113 +38,57 @@ namespace fuzztest::internal { -#if defined(FUZZTEST_STUB_STD_FILESYSTEM) - -// TODO(lszekeres): Return absl::Status instead of bool for these. - -bool WriteFile(absl::string_view path, absl::string_view contents) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -std::optional 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 ListDirectory(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; -} - -std::vector ListDirectoryRecursively(absl::string_view path) { - FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS"; +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); } -#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; + } + } + 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 !file.fail(); + return status.ok(); } std::optional 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(); -} - -bool IsDirectory(absl::string_view path) { - const std::filesystem::path fs_path{ - std::string_view{path.data(), path.size()}}; - - return std::filesystem::is_directory(fs_path); + return contents; } -bool CreateDirectory(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::create_directories(fs_path); -} +bool CreateDirectory(absl::string_view path) { return RemoteMkdir(path).ok(); } std::vector ListDirectory(absl::string_view path) { - std::vector 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 ListDirectoryRecursively(absl::string_view path) { - std::vector 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 += '/';