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 28, 2025
1 parent 76b60b8 commit 87a3ab9
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 2 deletions.
2 changes: 2 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 @@ -94,6 +95,7 @@ cc_test(
deps = [
":fuzztest_macros",
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)
Expand Down
25 changes: 25 additions & 0 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 @@ -87,6 +89,29 @@ std::vector<std::tuple<std::string>> ReadFilesFromDirectory(
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().string());
FUZZTEST_INTERNAL_CHECK_PRECONDITION(
stream.good(), "Cannot read input file: ", entry.path().string(), ": ",
strerror(errno));

std::stringstream buffer;
buffer << stream.rdbuf();
out.push_back({buffer.str()});
}
return out;
}

absl::StatusOr<std::vector<std::string>> ParseDictionary(
absl::string_view text) {
std::vector<std::string> parsed_entries;
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
67 changes: 67 additions & 0 deletions fuzztest/fuzztest_macros_test.cc
Original file line number Diff line number Diff line change
@@ -1,16 +1,83 @@
#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/status/status.h"
#include "absl/strings/match.h"

namespace fuzztest::internal {
namespace {

namespace fs = std::filesystem;

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

class ReadFilesFromDirectoryTest : public ::testing::Test {
protected:
void SetUp() override {
temp_dir_ = fs::path(testing::TempDir()) / "read_files_from_directory_test";
fs::create_directories(temp_dir_);
}

void TearDown() override { fs::remove_all(temp_dir_); }

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

fs::path temp_dir_;
};

TEST_F(ReadFilesFromDirectoryTest, NoFilterReturnsEverything) {
WriteFile(temp_dir_ / "file-1.txt", "content-1");
WriteFile(temp_dir_ / "file-2.txt", "content-2");

auto seeds = ReadFilesFromDirectory(
temp_dir_.string(), [](std::string_view name) { return true; });

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

TEST_F(ReadFilesFromDirectoryTest, DirectoryIsTraversedRecursively) {
WriteFile(temp_dir_ / "file-1.txt", "content-1");
fs::create_directories(temp_dir_ / "sub-dir");
WriteFile(temp_dir_ / "sub-dir" / "file-2.txt", "content-2");

auto seeds = ReadFilesFromDirectory(
temp_dir_.string(), [](std::string_view name) { return true; });

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

TEST_F(ReadFilesFromDirectoryTest, FilterReturnsOnlyMatchingFiles) {
WriteFile(temp_dir_ / "file.png", "image");
WriteFile(temp_dir_ / "file.txt", "text");

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

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

TEST_F(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 87a3ab9

Please sign in to comment.