diff --git a/.cspell/words.txt b/.cspell/words.txt index 2d991326..691b7d2b 100644 --- a/.cspell/words.txt +++ b/.cspell/words.txt @@ -18,6 +18,7 @@ cflags chrono clsid cmake_current_source_dir +cmdc coinit_apartmentthreaded comdlg32 conin$ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc36a8b..ff90c326 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Issues * \#12 [unit test] Complete all providers unit tests +* \#22 [unit test] Complete FUSE unit tests * \#33 Complete initial v2.0 documentation * \#34 Add macOS support * \#38 Pinning a file should automatically initiate a download to cache diff --git a/repertory/repertory_test/src/fuse_drive_rename_test.cpp b/repertory/repertory_test/src/fuse_drive_rename_test.cpp index 0b654c7c..fdc48c1b 100644 --- a/repertory/repertory_test/src/fuse_drive_rename_test.cpp +++ b/repertory/repertory_test/src/fuse_drive_rename_test.cpp @@ -23,12 +23,294 @@ #include "fixtures/drive_fixture.hpp" +namespace { +void overwrite_text(const std::string &path, const char *s) { + int fd = ::open(path.c_str(), O_WRONLY | O_TRUNC); + ASSERT_NE(fd, -1); + write_all(fd, s, std::strlen(s)); + ::close(fd); +} + +void write_all(int fd, const char *s, size_t n) { + size_t off = 0; + while (off < n) { + ssize_t w = ::write(fd, s + off, n - off); + ASSERT_NE(w, -1); + off += static_cast(w); + } +} + +[[nodiscard]] auto slurp(const std::string &path) -> std::string { + int fd = ::open(path.c_str(), O_RDONLY); + if (fd == -1) + return {}; + std::string out; + char buf[4096]; + for (;;) { + ssize_t r = ::read(fd, buf, sizeof(buf)); + if (r == 0) + break; + if (r == -1) { + if (errno == EINTR) + continue; + break; + } + out.append(buf, buf + r); + } + + ::close(fd); + return out; +} +} // namespace + namespace repertory { TYPED_TEST_SUITE(fuse_test, platform_provider_types); -TYPED_TEST(fuse_test, can_rename_a_file) {} +TYPED_TEST(fuse_test, can_rename_a_file) { + std::string src_file_name{"rename_test"}; + auto src = this->create_file_and_test(src_file_name); -TYPED_TEST(fuse_test, can_rename_a_directory) {} + std::string dest_file_name{"rename_test_2"}; + auto dst = this->create_file_and_test(dest_file_name); + + errno = 0; + ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str())); + + errno = 0; + EXPECT_EQ(-1, ::access(src.c_str(), F_OK)); + EXPECT_EQ(ENOENT, errno); + + struct stat st_unix{}; + ASSERT_EQ(0, ::stat(dst.c_str(), &st_unix)); + EXPECT_TRUE(S_ISREG(st_unix.st_mode)); + + this->unlink_file_and_test(dst); +} + +TYPED_TEST(fuse_test, can_rename_a_directory) { + std::string src_dir_name{"rename_test"}; + auto src_dir = this->create_directory_and_test(src_dir_name); + + std::string dest_dir_name{"rename_test_2"}; + auto dst_dir = this->create_directory_and_test(dest_dir_name); + + errno = 0; + ASSERT_EQ(0, ::rename(src_dir.c_str(), dst_dir.c_str())); + + errno = 0; + EXPECT_EQ(-1, ::access(src_dir.c_str(), F_OK)); + EXPECT_EQ(ENOENT, errno); + + struct stat st{}; + ASSERT_EQ(0, ::stat(dst_dir.c_str(), &st)); + EXPECT_TRUE(S_ISDIR(st.st_mode)); + + this->rmdir_and_test(dst_dir); +} + +/* TYPED_TEST(fuse_test, rename_file_overwrite_existing) { + std::string src_name{"rename_overwrite_src.txt"}; + std::string dst_name{"rename_overwrite_dst.txt"}; + auto src = this->create_file_and_test(src_name); + auto dst = this->create_file_and_test(dst_name); + + overwrite_text(src, "SRC"); + overwrite_text(dst, "DST"); + + errno = 0; + ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str())); + + errno = 0; + EXPECT_EQ(-1, ::access(src.c_str(), F_OK)); + EXPECT_EQ(ENOENT, errno); + + EXPECT_EQ("SRC", slurp(dst)); + + this->unlink_file_and_test(dst); +} + +TYPED_TEST(fuse_test, rename_file_cross_directory) { + auto dir1 = this->create_directory_and_test("dir_1"); + auto dir2 = this->create_directory_and_test("dir_2"); + + auto src = this->create_file_and_test("dir_1/file.txt"); + std::string dst = utils::path::combine(dir2, {"moved.txt"}); + + overwrite_text(src, "CMDC"); + + errno = 0; + ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str())); + + errno = 0; + EXPECT_EQ(-1, ::access(src.c_str(), F_OK)); + EXPECT_EQ(ENOENT, errno); + EXPECT_EQ("CMDC", slurp(dst)); + + this->unlink_file_and_test(dst); + this->rmdir_and_test(dir1); + this->rmdir_and_test(dir2); +} + +TYPED_TEST(fuse_test, rename_file_same_path) { + auto src = this->create_file_and_test("rn_same.txt"); + overwrite_text(src, "CMDC"); + + errno = 0; + EXPECT_EQ(0, ::rename(src.c_str(), src.c_str())); + EXPECT_EQ("CMDC", slurp(src)); + + this->unlink_file_and_test(src); +} + +TYPED_TEST(fuse_test, rename_file_source_missing_sets_errno) { + auto dst = this->create_file_and_test("rn_any_dest.txt"); + auto missing = this->mount_location() + "/rn_missing_src.txt"; + + errno = 0; + EXPECT_EQ(-1, ::rename(missing.c_str(), dst.c_str())); + EXPECT_EQ(ENOENT, errno); + + this->unlink_file_and_test(dst); +} + +TYPED_TEST(fuse_test, rename_file_destination_parent_missing_sets_errno) { + auto src = this->create_file_and_test("rn_good_src.txt"); + auto dst = this->mount_location() + "/rn_missing_parent/rn_dest.txt"; + + errno = 0; + EXPECT_EQ(-1, ::rename(src.c_str(), dst.c_str())); + EXPECT_EQ(ENOENT, errno); + + this->unlink_file_and_test(src); +} + +TYPED_TEST(fuse_test, rename_file_into_existing_directory_sets_eisdir) { + auto src = this->create_file_and_test("rn_file.txt"); + auto dstdir = this->create_directory_and_test("rn_dest_dir"); + + errno = 0; + EXPECT_EQ(-1, ::rename(src.c_str(), dstdir.c_str())); + EXPECT_EQ(EISDIR, errno); + + this->unlink_file_and_test(src); + this->rmdir_and_test(dstdir); +} + +TYPED_TEST(fuse_test, rename_file_no_write_on_source_parent_sets_eacces) { + auto srcdir = this->create_directory_and_test("rn_srcp"); + auto dstdir = this->create_directory_and_test("rn_dstp"); + auto src = this->create_file_and_test("rn_srcp/a.txt"); + auto dst = dstdir + "/b.txt"; + + ASSERT_EQ(0, ::chmod(srcdir.c_str(), 0555)); + + errno = 0; + EXPECT_EQ(-1, ::rename(src.c_str(), dst.c_str())); + EXPECT_EQ(EACCES, errno); + + ASSERT_EQ(0, ::chmod(srcdir.c_str(), 0755)); + + this->unlink_file_and_test(src); + this->rmdir_and_test(srcdir); + this->rmdir_and_test(dstdir); +} + +TYPED_TEST(fuse_test, rename_file_no_write_on_destination_parent_sets_eacces) { + auto srcdir = this->create_directory_and_test("rn_srcp2"); + auto dstdir = this->create_directory_and_test("rn_dstp2"); + auto src = this->create_file_and_test("rn_srcp2/a.txt"); + auto dst = dstdir + "/b.txt"; + + ASSERT_EQ(0, ::chmod(dstdir.c_str(), 0555)); + + errno = 0; + EXPECT_EQ(-1, ::rename(src.c_str(), dst.c_str())); + EXPECT_EQ(EACCES, errno); + + ASSERT_EQ(0, ::chmod(dstdir.c_str(), 0755)); + + this->unlink_file_and_test(src); + this->rmdir_and_test(srcdir); + this->rmdir_and_test(dstdir); +} + +TYPED_TEST(fuse_test, rename_file_overwrite_readonly_destination_succeeds) { + auto src = this->create_file_and_test("rn_src_ro.txt"); + auto dst = this->create_file_and_test("rn_dst_ro.txt"); + + overwrite_text(src, "NEW"); + overwrite_text(dst, "OLD"); + + ASSERT_EQ(0, ::chmod(dst.c_str(), 0444)); + + errno = 0; + int rn = ::rename(src.c_str(), dst.c_str()); + if (rn == -1 && errno == EROFS) { + this->unlink_file_and_test(src); + ASSERT_EQ(0, ::chmod(dst.c_str(), 0644)); + this->unlink_file_and_test(dst); + GTEST_SKIP(); + } + ASSERT_EQ(0, rn); + + EXPECT_EQ("NEW", slurp(dst)); + + ASSERT_EQ(0, ::chmod(dst.c_str(), 0644)); + this->unlink_file_and_test(dst); +} + +TYPED_TEST(fuse_test, rename_file_on_readonly_mount_sets_erofs_or_skip) { + auto src = this->create_file_and_test("rn_ro_src.txt"); + auto dst = this->mount_location() + "/rn_ro_dst.txt"; + + if (::rename(src.c_str(), src.c_str()) == 0) { + this->unlink_file_and_test(src); + GTEST_SKIP(); + } + + errno = 0; + EXPECT_EQ(-1, ::rename(src.c_str(), dst.c_str())); + EXPECT_EQ(EROFS, errno); + + this->unlink_file_and_test(src); +} + +TYPED_TEST(fuse_test, rename_file_open_fd_remains_valid) { + auto src = this->create_file_and_test("rn_fd_src.txt"); + auto dst = this->mount_location() + "/rn_fd_dst.txt"; + + overwrite_text(src, "HELLO"); + + int fd = ::open(src.c_str(), O_RDWR); + ASSERT_NE(fd, -1); + + errno = 0; + ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str())); + + errno = 0; + EXPECT_EQ(-1, ::access(src.c_str(), F_OK)); + EXPECT_EQ(ENOENT, errno); + + ASSERT_NE(-1, ::lseek(fd, 0, SEEK_END)); + write_all(fd, " WORLD", 6); + ::close(fd); + + EXPECT_EQ("HELLO WORLD", slurp(dst)); + this->unlink_file_and_test(dst); +} + +TYPED_TEST(fuse_test, rename_file_destination_component_name_too_long) { + auto src = this->create_file_and_test("rn_ntl_src.txt"); + + std::string longname(300, 'a'); + std::string dst = this->mount_location() + "/" + longname; + + errno = 0; + EXPECT_EQ(-1, ::rename(src.c_str(), dst.c_str())); + EXPECT_TRUE(errno == ENAMETOOLONG || errno == EINVAL); + + this->unlink_file_and_test(src); +} */ } // namespace repertory #endif // !defined(_WIN32)