#include "io.hh" #include "io_test_helper.hh" #include #include #include #include #include #include #include #include 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); }