Skip to content

Commit

Permalink
Add a version of ReadFilesFromDirectory that allows filtering by file…
Browse files Browse the repository at this point in the history
… name.

Based on #1520.

PiperOrigin-RevId: 719678199
  • Loading branch information
lszekeres authored and copybara-github committed Jan 31, 2025
1 parent f020847 commit f40443d
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 4 deletions.
4 changes: 4 additions & 0 deletions fuzztest/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ cc_library(
],
deps = [
":io",
":logging",
":registration",
":registry",
"@com_google_absl//absl/log:check",
Expand All @@ -93,7 +94,10 @@ cc_test(
srcs = ["fuzztest_macros_test.cc"],
deps = [
":fuzztest_macros",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_fuzztest//common:temp_dir",
"@com_google_googletest//:gtest_main",
],
)
Expand Down
17 changes: 17 additions & 0 deletions fuzztest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,27 @@ fuzztest_cc_library(
"fuzztest_macros.cc"
DEPS
fuzztest::io
fuzztest::logging
fuzztest::registration
fuzztest::registry
)

fuzztest_cc_test(
NAME
fuzztest_macros_test
HDRS
"fuzztest_macros_test.h"
SRCS
"fuzztest_macros_test.cc"
DEPS
GTest::gmock_main
absl::log
absl::status
absl::strings
fuzztest::fuzztest_macros
fuzztest::temp_dir
)

fuzztest_cc_library(
NAME
fuzztest_gtest_main
Expand Down
29 changes: 27 additions & 2 deletions fuzztest/fuzztest_macros.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <cstring>
#include <filesystem> // NOLINT
#include <fstream>
#include <functional>
#include <sstream>
#include <string>
#include <string_view>
Expand All @@ -18,6 +19,7 @@
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "./fuzztest/internal/io.h"
#include "./fuzztest/internal/logging.h"

namespace fuzztest {

Expand Down Expand Up @@ -71,7 +73,7 @@ std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
for (const auto& entry :
std::filesystem::recursive_directory_iterator(fs_dir)) {
if (std::filesystem::is_directory(entry)) continue;
std::ifstream stream(entry.path().string());
std::ifstream stream(entry.path());
if (!stream.good()) {
// Using stderr instead of GetStderr() to avoid
// initialization-order-fiasco when reading files at static init time with
Expand All @@ -82,7 +84,30 @@ std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
}
std::stringstream buffer;
buffer << stream.rdbuf();
out.push_back({buffer.str()});
out.emplace_back(std::move(buffer).str());
}
return out;
}

std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
std::string_view dir, std::function<bool(std::string_view)> filter) {
std::vector<std::tuple<std::string>> out;
const std::filesystem::path fs_dir(dir);
FUZZTEST_INTERNAL_CHECK_PRECONDITION(std::filesystem::is_directory(fs_dir),
"Not a directory: ", fs_dir.string());
for (const auto& entry :
std::filesystem::recursive_directory_iterator(fs_dir)) {
if (std::filesystem::is_directory(entry)) continue;
if (!filter(entry.path().string())) continue;

std::ifstream stream(entry.path());
FUZZTEST_INTERNAL_CHECK_PRECONDITION(
stream.good(), "Cannot read input file: ", entry.path().string(), ": ",
strerror(errno));

std::stringstream buffer;
buffer << stream.rdbuf();
out.emplace_back(std::move(buffer).str());
}
return out;
}
Expand Down
24 changes: 22 additions & 2 deletions fuzztest/fuzztest_macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#define FUZZTEST_FUZZTEST_FUZZTEST_MACROS_H_

#include <cstdint>
#include <functional>
#include <string>
#include <string_view>
#include <tuple>
Expand Down Expand Up @@ -114,8 +115,8 @@ namespace fuzztest {
#define FUZZ_TEST_F(fixture, func) \
INTERNAL_FUZZ_TEST_F(fixture, func, fixture, func)

// Reads files as strings from the directory `dir` and returns a vector usable
// by .WithSeeds().
// Reads files from the directory `dir` recursively. Returns the content strings
// as a vector usable by .WithSeeds().
//
// Example:
//
Expand All @@ -124,9 +125,28 @@ namespace fuzztest {
// }
// FUZZ_TEST(MySuite, MyThingNeverCrashes)
// .WithSeeds(ReadFilesFromDirectory(kCorpusPath));
//
// TODO(b/380934093): Rewrite this function as ReadFilesFromDirectory(dir,
// [](std::string_view name) { return true; });
std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
std::string_view dir);

// Reads files from the directory `dir` recursively, if the file name matches
// the `filter` function. Returns the content strings as a vector usable by
// .WithSeeds().
//
// For example to read .xml files as string seeds:
//
// void MyThingNeverCrashes(const std::string& xml) {
// DoThingsWith(xml);
// }
// FUZZ_TEST(MySuite, MyThingNeverCrashes)
// .WithSeeds(ReadFilesFromDirectory(
// kCorpusPath,
// [](std::string_view name) { return absl::EndsWith(name, ".xml"; });
std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
std::string_view dir, std::function<bool(std::string_view)> filter);

// Returns parsed dictionary entries from fuzzer dictionary definition in the
// format specified at https://llvm.org/docs/LibFuzzer.html#dictionaries.
// If dictionary is in wrong format, return error status.
Expand Down
60 changes: 60 additions & 0 deletions fuzztest/fuzztest_macros_test.cc
Original file line number Diff line number Diff line change
@@ -1,16 +1,76 @@
#include "./fuzztest/fuzztest_macros.h"

#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/strings/match.h"
#include "./common/temp_dir.h"

namespace fuzztest::internal {
namespace {

namespace fs = std::filesystem;

using ::testing::ElementsAre;
using ::testing::FieldsAre;
using ::testing::UnorderedElementsAre;

void WriteFile(const std::string& file_name, const std::string& contents) {
std::ofstream f(file_name);
CHECK(f.is_open());
f << contents;
f.close();
}

TEST(ReadFilesFromDirectoryTest, NoFilterReturnsEverything) {
TempDir temp_dir;
WriteFile(temp_dir.path() / "file-1.txt", "content-1");
WriteFile(temp_dir.path() / "file-2.txt", "content-2");

auto seeds = ReadFilesFromDirectory(
temp_dir.path().c_str(), [](std::string_view name) { return true; });

EXPECT_THAT(seeds, UnorderedElementsAre(FieldsAre("content-1"),
FieldsAre("content-2")));
}

TEST(ReadFilesFromDirectoryTest, DirectoryIsTraversedRecursively) {
TempDir temp_dir;
WriteFile(temp_dir.path() / "file-1.txt", "content-1");
fs::create_directories(temp_dir.path() / "sub-dir");
WriteFile(temp_dir.path() / "sub-dir" / "file-2.txt", "content-2");

auto seeds = ReadFilesFromDirectory(
temp_dir.path().c_str(), [](std::string_view name) { return true; });

EXPECT_THAT(seeds, UnorderedElementsAre(FieldsAre("content-1"),
FieldsAre("content-2")));
}

TEST(ReadFilesFromDirectoryTest, FilterReturnsOnlyMatchingFiles) {
TempDir temp_dir;
WriteFile(temp_dir.path() / "file.png", "image");
WriteFile(temp_dir.path() / "file.txt", "text");

auto seeds = ReadFilesFromDirectory(
temp_dir.path().c_str(),
[](std::string_view name) { return absl::EndsWith(name, ".png"); });

EXPECT_THAT(seeds, UnorderedElementsAre(FieldsAre("image")));
}

TEST(ReadFilesFromDirectoryTest, DiesOnInvalidDirectory) {
EXPECT_DEATH(ReadFilesFromDirectory(
"invalid_dir", [](std::string_view name) { return true; }),
"Not a directory: invalid_dir");
}

TEST(ParseDictionaryTest, Success) {
// Derived from https://llvm.org/docs/LibFuzzer.html#dictionaries
Expand Down

0 comments on commit f40443d

Please sign in to comment.