[unit test] Complete FUSE unit tests #22
Some checks are pending
BlockStorage/repertory/pipeline/head Build queued...
Blockstorage/repertory/pipeline/head This commit looks good

This commit is contained in:
2025-09-20 08:43:35 -05:00
parent 6b1891741b
commit f7d4fe00ba
5 changed files with 290 additions and 86 deletions

View File

@@ -5,6 +5,7 @@ _sh_denyrd
_sh_denyrw
_spawnv
aarch64
abcdefgh
advapi32
armv8
autogen

View File

@@ -496,6 +496,56 @@ public:
unlink_file_and_test(file_path);
}
static void overwrite_text(const std::string &path, const std::string &data) {
int desc = ::open(path.c_str(), O_WRONLY | O_TRUNC);
ASSERT_NE(desc, -1);
write_all(desc, data);
::close(desc);
}
static void write_all(int desc, const std::string &data) {
std::size_t off{0U};
while (off < data.size()) {
auto written = ::write(desc, &data.at(off), data.length() - off);
ASSERT_NE(written, -1);
off += static_cast<size_t>(written);
}
}
static auto slurp(const std::string &path) -> std::string {
int desc = ::open(path.c_str(), O_RDONLY);
if (desc == -1) {
return {};
}
std::string out;
std::array<char, 4096U> buf{};
for (;;) {
auto bytes_read = ::read(desc, buf.data(), buf.size());
if (bytes_read == 0) {
break;
}
if (bytes_read == -1) {
if (errno == EINTR) {
continue;
}
break;
}
out.append(buf.begin(), std::next(buf.begin(), bytes_read));
}
::close(desc);
return out;
}
[[nodiscard]] static auto stat_size(const std::string &path) -> off_t {
struct stat st_unix{};
int res = ::stat(path.c_str(), &st_unix);
EXPECT_EQ(0, res) << "stat(" << path
<< ") failed: " << std::strerror(errno);
return st_unix.st_size;
}
#endif // !defined(_WIN32)
};

View File

@@ -23,60 +23,13 @@
#include "fixtures/drive_fixture.hpp"
namespace {
void overwrite_text(const std::string &path, const std::string &data);
void write_all(int desc, const std::string &data);
[[nodiscard]] auto slurp(const std::string &path) -> std::string;
void overwrite_text(const std::string &path, const std::string &data) {
int desc = ::open(path.c_str(), O_WRONLY | O_TRUNC);
ASSERT_NE(desc, -1);
write_all(desc, data);
::close(desc);
}
void write_all(int desc, const std::string &data) {
std::size_t off{0U};
while (off < data.size()) {
auto written = ::write(desc, &data.at(off), data.length() - off);
ASSERT_NE(written, -1);
off += static_cast<size_t>(written);
}
}
auto slurp(const std::string &path) -> std::string {
int desc = ::open(path.c_str(), O_RDONLY);
if (desc == -1) {
return {};
}
std::string out;
std::array<char, 4096U> buf{};
for (;;) {
auto bytes_read = ::read(desc, buf.data(), buf.size());
if (bytes_read == 0) {
break;
}
if (bytes_read == -1) {
if (errno == EINTR) {
continue;
}
break;
}
out.append(buf.begin(), std::next(buf.begin(), bytes_read));
}
::close(desc);
return out;
}
} // namespace
namespace repertory {
TYPED_TEST_SUITE(fuse_test, platform_provider_types);
TYPED_TEST(fuse_test, rename_can_rename_a_file) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -103,6 +56,7 @@ TYPED_TEST(fuse_test, rename_can_rename_a_file) {
TYPED_TEST(fuse_test, rename_can_rename_a_directory) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -129,6 +83,7 @@ TYPED_TEST(fuse_test, rename_can_rename_a_directory) {
TYPED_TEST(fuse_test, rename_can_overwrite_existing_file) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -138,8 +93,8 @@ TYPED_TEST(fuse_test, rename_can_overwrite_existing_file) {
std::string dst_name{"rename2.txt"};
auto dst = this->create_file_and_test(dst_name);
overwrite_text(src, "SRC");
overwrite_text(dst, "DST");
this->overwrite_text(src, "SRC");
this->overwrite_text(dst, "DST");
errno = 0;
ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str()));
@@ -148,7 +103,7 @@ TYPED_TEST(fuse_test, rename_can_overwrite_existing_file) {
EXPECT_EQ(-1, ::access(src.c_str(), F_OK));
EXPECT_EQ(ENOENT, errno);
EXPECT_EQ("SRC", slurp(dst));
EXPECT_EQ("SRC", this->slurp(dst));
this->unlink_file_and_test(dst);
}
@@ -156,6 +111,7 @@ TYPED_TEST(fuse_test, rename_can_overwrite_existing_file) {
TYPED_TEST(fuse_test, rename_can_rename_file_into_different_directory) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -169,7 +125,7 @@ TYPED_TEST(fuse_test, rename_can_rename_file_into_different_directory) {
auto src = this->create_file_and_test(file_name);
std::string dst = utils::path::combine(dir2, {"moved.txt"});
overwrite_text(src, "CMDC");
this->overwrite_text(src, "CMDC");
errno = 0;
ASSERT_EQ(0, ::rename(src.c_str(), dst.c_str()));
@@ -177,7 +133,7 @@ TYPED_TEST(fuse_test, rename_can_rename_file_into_different_directory) {
errno = 0;
EXPECT_EQ(-1, ::access(src.c_str(), F_OK));
EXPECT_EQ(ENOENT, errno);
EXPECT_EQ("CMDC", slurp(dst));
EXPECT_EQ("CMDC", this->slurp(dst));
this->unlink_file_and_test(dst);
this->rmdir_and_test(dir1);
@@ -187,16 +143,17 @@ TYPED_TEST(fuse_test, rename_can_rename_file_into_different_directory) {
TYPED_TEST(fuse_test, rename_can_rename_file_to_same_path) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
std::string file_name{"rename"};
auto src = this->create_file_and_test(file_name);
overwrite_text(src, "CMDC");
this->overwrite_text(src, "CMDC");
errno = 0;
EXPECT_EQ(0, ::rename(src.c_str(), src.c_str()));
EXPECT_EQ("CMDC", slurp(src));
EXPECT_EQ("CMDC", this->slurp(src));
this->unlink_file_and_test(src);
}
@@ -204,6 +161,7 @@ TYPED_TEST(fuse_test, rename_can_rename_file_to_same_path) {
TYPED_TEST(fuse_test, rename_file_fails_if_source_file_does_not_exist) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -225,6 +183,7 @@ TYPED_TEST(fuse_test,
rename_file_fails_if_destination_directory_does_not_exist) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -246,6 +205,7 @@ TYPED_TEST(fuse_test,
TYPED_TEST(fuse_test, rename_file_fails_if_destination_is_directory) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -268,6 +228,7 @@ TYPED_TEST(fuse_test, rename_file_fails_if_destination_is_directory) {
TYPED_TEST(fuse_test, rename_file_fails_if_source_directory_is_read_only) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -299,6 +260,7 @@ TYPED_TEST(fuse_test, rename_file_fails_if_source_directory_is_read_only) {
TYPED_TEST(fuse_test, rename_file_fails_if_destination_directory_is_read_only) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -330,6 +292,7 @@ TYPED_TEST(fuse_test, rename_file_fails_if_destination_directory_is_read_only) {
TYPED_TEST(fuse_test, rename_file_succeeds_if_destination_file_is_read_only) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -339,8 +302,8 @@ TYPED_TEST(fuse_test, rename_file_succeeds_if_destination_file_is_read_only) {
std::string dest_file_name{"rename_test_2"};
auto dst = this->create_file_and_test(dest_file_name);
overwrite_text(src, "NEW");
overwrite_text(dst, "OLD");
this->overwrite_text(src, "NEW");
this->overwrite_text(dst, "OLD");
ASSERT_EQ(0, ::chmod(dst.c_str(), 0444));
@@ -354,7 +317,7 @@ TYPED_TEST(fuse_test, rename_file_succeeds_if_destination_file_is_read_only) {
}
ASSERT_EQ(0, res);
EXPECT_EQ("NEW", slurp(dst));
EXPECT_EQ("NEW", this->slurp(dst));
ASSERT_EQ(0, ::chmod(dst.c_str(), 0644));
this->unlink_file_and_test(dst);
@@ -363,6 +326,7 @@ TYPED_TEST(fuse_test, rename_file_succeeds_if_destination_file_is_read_only) {
TYPED_TEST(fuse_test, rename_file_retains_open_file_descriptor) {
if (this->current_provider != provider_type::sia) {
// TODO finish test
GTEST_SKIP();
return;
}
@@ -372,7 +336,7 @@ TYPED_TEST(fuse_test, rename_file_retains_open_file_descriptor) {
std::string dest_file_name{"rename_test_2"};
auto dst = this->create_file_and_test(dest_file_name);
overwrite_text(src, "HELLO");
this->overwrite_text(src, "HELLO");
int desc = ::open(src.c_str(), O_RDWR);
ASSERT_NE(desc, -1);
@@ -385,10 +349,10 @@ TYPED_TEST(fuse_test, rename_file_retains_open_file_descriptor) {
EXPECT_EQ(ENOENT, errno);
ASSERT_NE(-1, ::lseek(desc, 0, SEEK_END));
write_all(desc, " WORLD");
this->write_all(desc, " WORLD");
::close(desc);
EXPECT_EQ("HELLO WORLD", slurp(dst));
EXPECT_EQ("HELLO WORLD", this->slurp(dst));
this->unlink_file_and_test(dst);
}

View File

@@ -1,28 +1,3 @@
// static void test_truncate(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// EXPECT_EQ(0, truncate(file_path.c_str(), 10u));
//
// std::uint64_t file_size{};
// EXPECT_TRUE(utils::file::get_file_size(file_path, file_size));
//
// EXPECT_EQ(std::uint64_t(10u), file_size);
// }
//
// static void test_ftruncate(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// auto fd = open(file_path.c_str(), O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP);
// EXPECT_LE(1, fd);
//
// EXPECT_EQ(0, ftruncate(fd, 10u));
//
// std::uint64_t file_size{};
// EXPECT_TRUE(utils::file::get_file_size(file_path, file_size));
//
// EXPECT_EQ(std::uint64_t(10u), file_size);
//
// close(fd);
// }
//
// #if !defined(__APPLE__)
// static void test_fallocate(const std::string & /* api_path */,
// const std::string &file_path) {

View File

@@ -0,0 +1,214 @@
/*
Copyright <2018-2025> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#if !defined(_WIN32)
#include "fixtures/drive_fixture.hpp"
namespace repertory {
TYPED_TEST_SUITE(fuse_test, platform_provider_types);
TYPED_TEST(fuse_test, truncate_can_shrink_file) {
std::string file_name{"truncate"};
auto src = this->create_file_and_test(file_name);
this->overwrite_text(src, "ABCDEFGH");
errno = 0;
ASSERT_EQ(0, ::truncate(src.c_str(), 3));
EXPECT_EQ(3, this->stat_size(src));
EXPECT_EQ("ABC", this->slurp(src));
this->unlink_file_and_test(src);
}
TYPED_TEST(fuse_test, truncate_expand_file_is_zero_filled) {
std::string name{"truncate"};
auto src = this->create_file_and_test(name);
this->overwrite_text(src, "XYZ");
errno = 0;
ASSERT_EQ(0, ::truncate(src.c_str(), 10));
EXPECT_EQ(10, this->stat_size(src));
auto data = this->slurp(src);
ASSERT_EQ(10U, data.size());
EXPECT_EQ('X', data.at(0U));
EXPECT_EQ('Y', data.at(1U));
EXPECT_EQ('Z', data.at(2U));
for (std::size_t idx = 3; idx < data.size(); ++idx) {
EXPECT_EQ('\0', data[idx]);
}
this->unlink_file_and_test(src);
}
TYPED_TEST(fuse_test, truncate_fails_if_source_does_not_exist) {
std::string name{"truncate"};
auto src = this->create_file_path(name);
errno = 0;
EXPECT_EQ(-1, ::truncate(src.c_str(), 1));
EXPECT_EQ(ENOENT, errno);
EXPECT_FALSE(utils::file::file{src}.exists());
}
TYPED_TEST(fuse_test, truncate_fails_if_path_is_directory) {
std::string name{"truncate"};
auto src = this->create_directory_and_test(name);
errno = 0;
auto res = ::truncate(src.c_str(), 0);
EXPECT_EQ(-1, res);
EXPECT_TRUE(errno == EISDIR || errno == EPERM || errno == EACCES ||
errno == EINVAL);
this->rmdir_and_test(src);
}
/*
TYPED_TEST(fuse_test, truncate_readonly_file_eacces_or_eperm_or_erofs_skip) {
if (this->current_provider != provider_type::sia)
return;
std::string name{"trunc_ro"};
std::string p = this->create_file_and_test(name);
this->overwrite_text(p, "DATA");
ASSERT_EQ(0, ::chmod(p.c_str(), 0444)) << std::strerror(errno);
errno = 0;
int res = ::truncate(p.c_str(), 2);
if (res == -1 && errno == EROFS) {
ASSERT_EQ(0, ::chmod(p.c_str(), 0644));
this->unlink_file_and_test(p);
GTEST_SKIP() << "read-only mount; truncate returns EROFS";
}
EXPECT_EQ(-1, res);
EXPECT_TRUE(errno == EACCES || errno == EPERM)
<< "errno=" << errno << " " << std::strerror(errno);
ASSERT_EQ(0, ::chmod(p.c_str(), 0644));
this->unlink_file_and_test(p);
}
// ========== ftruncate(2) ==========
TYPED_TEST(fuse_test, ftruncate_shrink_open_fd) {
if (this->current_provider != provider_type::sia)
return;
std::string name{"ftrunc_shrink"};
std::string p = this->create_file_and_test(name);
this->overwrite_text(p, "HELLOWORLD"); // 10 bytes
int fd = ::open(p.c_str(), O_RDWR);
ASSERT_NE(fd, -1) << "open failed: " << std::strerror(errno);
errno = 0;
ASSERT_EQ(0, ::ftruncate(fd, 4)) << std::strerror(errno);
::close(fd);
EXPECT_EQ(4, this->stat_size(p));
EXPECT_EQ("HELL", this->slurp(p));
this->unlink_file_and_test(p);
}
TYPED_TEST(fuse_test, ftruncate_expand_open_fd_zero_fills) {
if (this->current_provider != provider_type::sia)
return;
std::string name{"ftrunc_expand"};
std::string p = this->create_file_and_test(name);
this->overwrite_text(p, "AA"); // 2 bytes
int fd = ::open(p.c_str(), O_RDWR);
ASSERT_NE(fd, -1);
errno = 0;
ASSERT_EQ(0, ::ftruncate(fd, 6)) << std::strerror(errno);
::close(fd);
EXPECT_EQ(6, this->stat_size(p));
auto s = this->slurp(p);
ASSERT_EQ(6u, s.size());
EXPECT_EQ('A', s[0]);
EXPECT_EQ('A', s[1]);
for (size_t i = 2; i < s.size(); ++i) {
EXPECT_EQ('\0', s[i]) << "byte " << i << " not zero";
}
this->unlink_file_and_test(p);
}
TYPED_TEST(fuse_test, ftruncate_readonly_fd_ebadf) {
if (this->current_provider != provider_type::sia)
return;
std::string name{"ftrunc_rofd"};
std::string p = this->create_file_and_test(name);
this->overwrite_text(p, "RW");
int fd = ::open(p.c_str(), O_RDONLY);
ASSERT_NE(fd, -1);
errno = 0;
EXPECT_EQ(-1, ::ftruncate(fd, 1));
EXPECT_EQ(EBADF, errno);
::close(fd);
this->unlink_file_and_test(p);
}
TYPED_TEST(fuse_test, ftruncate_on_ro_mount_erofs_or_skip) {
if (this->current_provider != provider_type::sia)
return;
std::string name{"ftrunc_ro_mount"};
std::string p = this->create_file_and_test(name);
this->overwrite_text(p, "X");
int fd = ::open(p.c_str(), O_RDWR);
if (fd == -1) {
this->unlink_file_and_test(p);
GTEST_SKIP() << "cannot open O_RDWR; probable RO mount";
}
errno = 0;
int res = ::ftruncate(fd, 0);
if (res == -1 && errno == EROFS) {
::close(fd);
this->unlink_file_and_test(p);
GTEST_SKIP() << "read-only mount; ftruncate returns EROFS";
}
ASSERT_EQ(0, res) << std::strerror(errno);
::close(fd);
this->unlink_file_and_test(p);
}
*/
} // namespace repertory
#endif // !defined(_WIN32)