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