blob: 7e6f51c85838571fc2972475c7a14c75eb71f347 [file] [log] [blame]
// Copyright 2022 Google LLC
//
// 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
//
// http://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 "./fuzztest/internal/io.h"
#include <sys/stat.h>
#include <cerrno>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem> // NOLINT
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "./common/blob_file.h"
#include "./common/defs.h"
#include "./common/logging.h"
#include "./common/temp_dir.h"
#include "./fuzztest/fuzztest_core.h"
namespace fuzztest::internal {
namespace {
using ::testing::Eq;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
using ::testing::Optional;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
std::string TmpFile(const std::string& name) {
std::string filename = absl::StrCat(testing::TempDir(), "/", name, "XXXXXX");
return mktemp(filename.data());
}
// These use a different implementation of read/write.
// Otherwise we are testing the functions with themselves.
std::string TestRead(const std::string& filename) {
FILE* f = fopen(filename.c_str(), "r");
char buf[1024]{};
size_t s = fread(buf, 1, sizeof(buf), f);
fclose(f);
return std::string(buf, s);
}
void TestWrite(const std::string& filename, const std::string& contents) {
FILE* f = fopen(filename.c_str(), "w");
ASSERT_TRUE(f) << strerror(errno);
EXPECT_EQ(fwrite(contents.data(), contents.size(), 1, f), 1)
<< strerror(errno);
EXPECT_EQ(0, fclose(f)) << strerror(errno);
}
TEST(IOTest, WriteFileWorksWhenDirectoryExists) {
const std::string tmp_name = TmpFile("write_test");
EXPECT_TRUE(WriteFile(tmp_name, "Payload1"));
EXPECT_EQ(TestRead(tmp_name), "Payload1");
std::filesystem::remove(tmp_name);
}
TEST(IOTest, WriteFileWorksWhenDirectoryDoesNotExist) {
TempDir tmp_dir;
const std::string tmp_name = tmp_dir.path() / "doesnt_exist" / "file";
EXPECT_TRUE(WriteFile(tmp_name, "Payload1"));
EXPECT_EQ(TestRead(tmp_name), "Payload1");
}
TEST(IOTest, WriteDataToDirReturnsWrittenFilePath) {
TempDir tmp_dir;
const std::string path = WriteDataToDir("data", tmp_dir.path().c_str());
EXPECT_THAT(ReadFile(path), Optional(Eq("data")));
}
TEST(IOTest, WriteDataToDirWritesToSameFileOnSameData) {
TempDir tmp_dir;
const std::string path = WriteDataToDir("data", tmp_dir.path().c_str());
EXPECT_THAT(WriteDataToDir("data", tmp_dir.path().c_str()), Eq(path));
EXPECT_THAT(ReadFile(path), Optional(Eq("data")));
}
TEST(IOTest, ReadFileReturnsNulloptWhenMissing) {
EXPECT_THAT(ReadFile("/doesnt_exist/file"), Eq(std::nullopt));
EXPECT_THAT(ReadFileOrDirectory("/doesnt_exist/file"),
UnorderedElementsAre());
}
TEST(IOTest, ReadFileWorksWhenFileExists) {
const std::string tmp_name = TmpFile("read_test");
TestWrite(tmp_name, "Payload2");
EXPECT_THAT(ReadFile(tmp_name), Optional(Eq("Payload2")));
EXPECT_THAT(ReadFileOrDirectory(tmp_name),
UnorderedElementsAre(FieldsAre(tmp_name, "Payload2")));
std::filesystem::remove(tmp_name);
}
TEST(IOTest, ReadFileOrDirectoryWorks) {
TempDir tmp_dir;
EXPECT_THAT(ReadFileOrDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre());
const std::string tmp_file_1 = tmp_dir.path() / "file1";
TestWrite(tmp_file_1, "Payload3.1");
EXPECT_THAT(ReadFileOrDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre(FieldsAre(tmp_file_1, "Payload3.1")));
const std::string tmp_file_2 = tmp_dir.path() / "file2";
TestWrite(tmp_file_2, "Payload3.2");
EXPECT_THAT(ReadFileOrDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre(FieldsAre(tmp_file_1, "Payload3.1"),
FieldsAre(tmp_file_2, "Payload3.2")));
}
TEST(IOTest, ReadFileOrDirectoryWorksRecursively) {
TempDir tmp_dir;
const std::string tmp_sub_dir = tmp_dir.path() / "subdir";
mkdir(tmp_sub_dir.c_str(), 0700);
const std::string tmp_file_1 = tmp_dir.path() / "file1";
TestWrite(tmp_file_1, "Payload5.1");
const std::string tmp_file_2 = absl::StrCat(tmp_sub_dir, "/file2");
TestWrite(tmp_file_2, "Payload5.2");
EXPECT_THAT(ReadFileOrDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre(FieldsAre(tmp_file_1, "Payload5.1"),
FieldsAre(tmp_file_2, "Payload5.2")));
}
TEST(IOTest, ReadFilesFromDirectoryWorks) {
TempDir tmp_dir;
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre());
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir.path().c_str()), SizeIs(0));
const std::string tmp_file_1 = tmp_dir.path() / "file1";
TestWrite(tmp_file_1, "Payload4.1");
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre(FieldsAre("Payload4.1")));
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir.path().c_str()), SizeIs(1));
const std::string tmp_file_2 = tmp_dir.path() / "file2";
TestWrite(tmp_file_2, "Payload4.2");
EXPECT_THAT(
ReadFilesFromDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre(FieldsAre("Payload4.1"), FieldsAre("Payload4.2")));
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir.path().c_str()), SizeIs(2));
}
TEST(IOTest, ReadFilesFromDirectoryReturnsEmptyVectorWhenNoFilesInDir) {
TempDir tmp_dir;
EXPECT_THAT(ReadFilesFromDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre());
EXPECT_THAT(ReadFileOrDirectory(tmp_dir.path().c_str()), SizeIs(0));
}
TEST(IOTest, ReadFilesFromDirectoryReturnsEmptyVectorWhenMissing) {
EXPECT_THAT(ReadFilesFromDirectory("/doesnt_exist/"), UnorderedElementsAre());
EXPECT_THAT(ReadFileOrDirectory("/doesnt_exist/"), SizeIs(0));
}
TEST(IOTest, ListDirectoryReturnsPathsInDirectory) {
TempDir tmp_dir;
const std::string tmp_file_1 = tmp_dir.path() / "file1";
TestWrite(tmp_file_1, /*contents=*/"File1");
const std::string tmp_file_2 = tmp_dir.path() / "file2";
TestWrite(tmp_file_2, /*contents=*/"File2");
EXPECT_THAT(ListDirectory(tmp_dir.path().c_str()),
UnorderedElementsAre(tmp_file_1, tmp_file_2));
}
TEST(IOTest, ListDirectoryReturnsEmptyVectorWhenDirectoryIsEmpty) {
TempDir tmp_dir;
EXPECT_THAT(ListDirectory(tmp_dir.path().c_str()), IsEmpty());
}
TEST(IOTest, ListDirectoryReturnsEmptyVectorWhenDirectoryDoesNotExist) {
EXPECT_THAT(ListDirectory("/doesnt_exist/"), IsEmpty());
}
TEST(ForEachSerializedInputTest, ReadsInputsFromSerializedFilesAndBlobFiles) {
TempDir tmp_dir;
const std::string serialized_file = tmp_dir.path() / "serialized_file";
const std::string blob_file = tmp_dir.path() / "blob_file";
TestWrite(serialized_file, "Input1");
std::unique_ptr<fuzztest::internal::BlobFileWriter> writer =
fuzztest::internal::DefaultBlobFileWriterFactory();
FUZZTEST_CHECK(writer->Open(blob_file, "w").ok());
FUZZTEST_CHECK(
writer->Write(fuzztest::internal::AsByteSpan(absl::string_view("Input2")))
.ok());
FUZZTEST_CHECK(
writer->Write(fuzztest::internal::AsByteSpan(absl::string_view("Input3")))
.ok());
FUZZTEST_CHECK(writer->Close().ok());
using InputInFile = std::tuple<std::string, std::optional<int>, std::string>;
std::vector<InputInFile> inputs;
ForEachSerializedInput({serialized_file, blob_file},
[&](absl::string_view file_path,
std::optional<int> blob_idx, std::string input) {
inputs.emplace_back(std::string(file_path), blob_idx,
std::move(input));
return absl::OkStatus();
});
EXPECT_THAT(inputs, UnorderedElementsAre(
InputInFile{serialized_file, std::nullopt, "Input1"},
InputInFile{blob_file, 0, "Input2"},
InputInFile{blob_file, 1, "Input3"}));
}
TEST(ForEachSerializedInputTest, IgnoresUnconsumedInputs) {
TempDir tmp_dir;
const std::string file = tmp_dir.path() / "file";
std::unique_ptr<fuzztest::internal::BlobFileWriter> writer =
fuzztest::internal::DefaultBlobFileWriterFactory();
FUZZTEST_CHECK(writer->Open(file, "w").ok());
FUZZTEST_CHECK(
writer->Write(fuzztest::internal::AsByteSpan(absl::string_view("Ignore")))
.ok());
FUZZTEST_CHECK(
writer->Write(fuzztest::internal::AsByteSpan(absl::string_view("Accept")))
.ok());
FUZZTEST_CHECK(writer->Close().ok());
using InputInFile = std::tuple<std::string, std::optional<int>, std::string>;
std::vector<InputInFile> inputs;
ForEachSerializedInput(
{file}, [&](absl::string_view file_path, std::optional<int> blob_idx,
std::string input) {
if (input == "Ignore") return absl::InvalidArgumentError("Ignore");
inputs.emplace_back(std::string(file_path), blob_idx, std::move(input));
return absl::OkStatus();
});
EXPECT_THAT(inputs, UnorderedElementsAre(InputInFile{file, 1, "Accept"}));
}
TEST(ForEachSerializedInputTest, ReadsInputsUntilTimeout) {
TempDir tmp_dir;
std::vector<std::string> serialized_files;
constexpr int kInputsNum = 1000;
constexpr absl::Duration kTimeout = absl::Seconds(1);
for (int i = 0; i < kInputsNum; ++i) {
serialized_files.push_back(tmp_dir.path() /
absl::StrCat("serialized_file", i));
TestWrite(serialized_files.back(), absl::StrCat("Input", i));
}
int inputs_read = 0;
ForEachSerializedInput(
serialized_files,
[&inputs_read, kTimeout](absl::string_view file_path,
std::optional<int> blob_idx, std::string input) {
absl::SleepFor(kTimeout / kInputsNum);
++inputs_read;
return absl::OkStatus();
},
kTimeout);
EXPECT_LT(inputs_read, kInputsNum);
}
TEST(ForEachSerializedInputTest, DiesOnDirectoriesInFilePaths) {
TempDir tmp_dir;
const std::string dir = tmp_dir.path() / "dir";
std::filesystem::create_directory(dir);
EXPECT_DEATH(ForEachSerializedInput(
{dir}, [&](absl::string_view, std::optional<int>,
std::string) { return absl::OkStatus(); }),
"is a directory");
}
TEST(ForEachSerializedInputTest, DiesOnNonExistingFilePaths) {
EXPECT_DEATH(
ForEachSerializedInput({"/doesnt_exist/file"},
[&](absl::string_view, std::optional<int>,
std::string) { return absl::OkStatus(); }),
"does not exist");
}
} // namespace
} // namespace fuzztest::internal