diff options
Diffstat (limited to 'test/io.cc')
| -rw-r--r-- | test/io.cc | 203 |
1 files changed, 203 insertions, 0 deletions
diff --git a/test/io.cc b/test/io.cc new file mode 100644 index 0000000..04d97d6 --- /dev/null +++ b/test/io.cc @@ -0,0 +1,203 @@ +#include "io.hh" + +#include "io_test_helper.hh" + +#include <cerrno> +#include <cstdlib> +#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 fd = openat(dirfd(), name.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0700); + EXPECT_NE(-1, fd); + if (fd == -1) + return; + size_t offset = 0; + while (offset < value.size()) { + auto ret = write(fd, value.data() + offset, value.size() - offset); + EXPECT_LT(0, ret); + if (ret <= 0) { + break; + } + offset += ret; + } + close(fd); + } + + 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); +} |
