diff options
Diffstat (limited to 'test/io.cc')
| -rw-r--r-- | test/io.cc | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/test/io.cc b/test/io.cc new file mode 100644 index 0000000..39d558b --- /dev/null +++ b/test/io.cc @@ -0,0 +1,274 @@ +#include "io.hh" + +#include "io_test_helper.hh" + +#include <cerrno> +#include <dirent.h> +#include <fcntl.h> +#include <gtest/gtest.h> +#include <sys/stat.h> +#include <unistd.h> +#include <utility> + +namespace { + +bool remove_recursive(int fd) { + auto* dir = fdopendir(fd); + if (!dir) + return false; + while (auto* ent = readdir(dir)) { + if (ent->d_name[0] == '.') { + if (ent->d_name[1] == '\0') + continue; + if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') + continue; + } + bool is_dir; + if (ent->d_type == DT_DIR) { + is_dir = true; + } else if (ent->d_type == DT_UNKNOWN) { + struct stat buf; + if (fstatat(dirfd(dir), ent->d_name, &buf, AT_SYMLINK_NOFOLLOW) == 0) { + is_dir = S_ISDIR(buf.st_mode); + } else { + if (errno != ENOENT) { + closedir(dir); + return false; + } + is_dir = false; + } + } else { + is_dir = false; + } + + if (is_dir) { + int fd2 = openat(dirfd(dir), ent->d_name, O_RDONLY | O_DIRECTORY); + if (fd2 == -1) { + if (errno != ENOENT) { + closedir(dir); + return false; + } + } else { + if (!remove_recursive(fd2)) { + closedir(dir); + return false; + } + } + } + if (unlinkat(dirfd(dir), ent->d_name, is_dir ? AT_REMOVEDIR : 0)) { + if (errno != ENOENT) { + closedir(dir); + return false; + } + } + } + closedir(dir); + return true; +} + +class IoTest : public testing::Test { + protected: + void SetUp() override { + // NOLINTNEXTLINE(misc-include-cleaner) + tmpdir_ = P_tmpdir "/jkc-test-io-XXXXXX"; + // NOLINTNEXTLINE(misc-include-cleaner) + auto* ret = mkdtemp(tmpdir_.data()); + ASSERT_EQ(ret, tmpdir_.data()); + dirfd_ = open(tmpdir_.c_str(), O_PATH | O_DIRECTORY); + ASSERT_NE(-1, dirfd_); + } + + void TearDown() override { + int fd = openat(dirfd_, ".", O_RDONLY | O_DIRECTORY); + EXPECT_NE(-1, fd); + if (fd != -1) { + EXPECT_TRUE(remove_recursive(fd)); + } + close(dirfd_); + rmdir(tmpdir_.c_str()); + } + + [[nodiscard]] int dirfd() const { return dirfd_; } + + void touch(const std::string& name, const std::string& value = "") { + auto ret = io::createat(dirfd(), name, false); + ASSERT_TRUE(ret.has_value()); + auto ret2 = ret.value()->repeat_write(value.data(), value.size()); + EXPECT_TRUE(ret2.has_value()); + if (ret2.has_value()) { + EXPECT_EQ(ret2.value(), value.size()); + } + EXPECT_TRUE(ret.value()->close().has_value()); + } + + void cat(const std::string& name, const std::string& value) { + auto ret = io::openat(dirfd(), name); + ASSERT_TRUE(ret.has_value()); + std::string tmp(value.size(), ' '); + auto ret2 = ret.value()->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(value.size(), ret2.value()); + EXPECT_EQ(tmp, value); + ret2 = ret.value()->repeat_read(tmp.data(), tmp.size()); + ASSERT_FALSE(ret2.has_value()); + EXPECT_EQ(io::ReadError::Eof, ret2.error()); + } + + private: + int dirfd_{-1}; + std::string tmpdir_; +}; + +} // namespace + +TEST_F(IoTest, no_such_file) { + auto ret = io::openat(dirfd(), "no-such-file"); + ASSERT_FALSE(ret.has_value()); + EXPECT_EQ(io::OpenError::NoSuchFile, ret.error()); +} + +TEST_F(IoTest, read_empty) { + touch("test"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + std::string tmp(10, ' '); + auto ret2 = ret.value()->read(tmp.data(), tmp.size()); + ASSERT_FALSE(ret2.has_value()); + EXPECT_EQ(io::ReadError::Eof, ret2.error()); +} + +TEST_F(IoTest, skip_empty) { + touch("test"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = ret.value()->skip(10); + ASSERT_FALSE(ret2.has_value()); + EXPECT_EQ(io::ReadError::Eof, ret2.error()); +} + +TEST_F(IoTest, read) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + std::string tmp(12, ' '); + auto ret2 = ret.value()->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(11, ret2.value()); + tmp.resize(ret2.value()); + EXPECT_EQ("hello world", tmp); +} + +TEST_F(IoTest, skip) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = ret.value()->repeat_skip(6); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(6, ret2.value()); + std::string tmp(12, ' '); + auto ret3 = ret.value()->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(5, ret3.value()); + tmp.resize(ret3.value()); + EXPECT_EQ("world", tmp); +} + +TEST_F(IoTest, read_block) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = io_make_max_block(std::move(ret.value()), 2); + std::string tmp(12, ' '); + auto ret3 = ret2->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(11, ret3.value()); + tmp.resize(ret3.value()); + EXPECT_EQ("hello world", tmp); +} + +TEST_F(IoTest, skip_block) { + touch("test", "hello world"); + + auto ret = io::openat(dirfd(), "test"); + ASSERT_TRUE(ret.has_value()); + auto ret2 = io_make_max_block(std::move(ret.value()), 2); + auto ret3 = ret2->repeat_skip(6); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(6, ret3.value()); + std::string tmp(12, ' '); + auto ret4 = ret2->repeat_read(tmp.data(), tmp.size()); + ASSERT_TRUE(ret4.has_value()); + EXPECT_EQ(5, ret4.value()); + tmp.resize(ret4.value()); + EXPECT_EQ("world", tmp); +} + +TEST_F(IoTest, create) { + auto ret = io::createat(dirfd(), "test", false); + ASSERT_TRUE(ret.has_value()); + std::string tmp{"hello world"}; + auto ret2 = ret.value()->repeat_write(tmp.data(), tmp.size()); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(11, ret2.value()); + EXPECT_TRUE(ret.value()->close().has_value()); + + cat("test", "hello world"); +} + +TEST_F(IoTest, create_exists) { + touch("test"); + + auto ret = io::createat(dirfd(), "test", false); + ASSERT_FALSE(ret.has_value()); + EXPECT_EQ(io::CreateError::Exists, ret.error()); +} + +TEST_F(IoTest, create_overwrite) { + touch("test"); + + auto ret = io::createat(dirfd(), "test", true); + ASSERT_TRUE(ret.has_value()); + std::string tmp{"hello world"}; + auto ret2 = ret.value()->repeat_write(tmp.data(), tmp.size()); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(11, ret2.value()); + EXPECT_TRUE(ret.value()->close().has_value()); + + cat("test", "hello world"); +} + +TEST_F(IoTest, write_block) { + auto ret = io::createat(dirfd(), "test", false); + ASSERT_TRUE(ret.has_value()); + auto ret2 = io_make_max_block(std::move(ret.value()), 2); + std::string tmp{"hello world"}; + auto ret3 = ret2->repeat_write(tmp.data(), tmp.size()); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(11, ret3.value()); + EXPECT_TRUE(ret2->close().has_value()); + + cat("test", "hello world"); +} + +TEST_F(IoTest, pipe) { + auto ret = io::pipe(); + ASSERT_TRUE(ret.has_value()); + + std::string tmp{"hello world"}; + auto ret2 = ret.value().second->repeat_write(tmp.data(), tmp.size()); + ASSERT_TRUE(ret2.has_value()); + EXPECT_EQ(11, ret2.value()); + ret.value().second.reset(); + + std::string tmp2(12, ' '); + auto ret3 = ret.value().first->repeat_read(tmp2.data(), tmp2.size()); + ASSERT_TRUE(ret3.has_value()); + EXPECT_EQ(11, ret3.value()); + tmp2.resize(ret3.value()); + EXPECT_EQ(tmp, tmp2); +} |
