v2.0.2-rc (#27)
Some checks reported errors
BlockStorage/repertory/pipeline/head Something is wrong with the build of this commit

## v2.0.2-rc

### BREAKING CHANGES

* Refactored `config.json` - will need to verify configuration settings prior to mounting

### Issues

* \#12 \[Unit Test\] Complete all providers unit tests
* \#14 \[Unit Test\] SQLite mini-ORM unit tests and cleanup
* \#16 Add support for bucket name in Sia provider
* \#17 Update to common c++ build system
  * A single 64-bit Linux Jenkins server is used to build all Linux and Windows versions
  * All dependency sources are now included
  * MSVC is no longer supported
  * MSYS2 is required for building Windows binaries on Windows
  * OS X support is temporarily disabled
* \#19 \[bug\] Rename file is broken for files that are existing
* \#23 \[bug\] Incorrect file size displayed while upload is pending
* \#24 RocksDB implementations should be transactional
* \#25 Writes should block when maximum cache size is reached
* \#26 Complete ring buffer and direct download support

### Changes from v2.0.1-rc

* Ability to choose between RocksDB and SQLite databases
* Added direct reads and implemented download fallback
* Corrected file times on S3 and Sia providers
* Corrected handling of `chown()` and `chmod()`
* Fixed erroneous download of chunks after resize

Reviewed-on: #27
This commit is contained in:
2024-12-28 15:56:40 -06:00
parent 1b8de3b097
commit 8dd46b8ad8
790 changed files with 49979 additions and 417734 deletions

View File

@@ -0,0 +1,70 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_FILE_DB_FIXTURE_HPP
#define REPERTORY_TEST_INCLUDE_FIXTURES_FILE_DB_FIXTURE_HPP
#include "test_common.hpp"
#include "app_config.hpp"
#include "db/impl/rdb_file_db.hpp"
#include "db/impl/sqlite_file_db.hpp"
#include "events/consumers/console_consumer.hpp"
#include "events/event_system.hpp"
namespace repertory {
template <typename db_t> class file_db_test : public ::testing::Test {
protected:
static std::unique_ptr<app_config> config;
static console_consumer console_;
static std::unique_ptr<db_t> file_db;
protected:
static void SetUpTestCase() {
static std::uint64_t idx{};
event_system::instance().start();
auto cfg_directory = utils::path::combine(test::get_test_output_dir(),
{
"file_db_test",
std::to_string(++idx),
});
config = std::make_unique<app_config>(provider_type::s3, cfg_directory);
file_db = std::make_unique<db_t>(*config);
}
static void TearDownTestCase() {
file_db.reset();
config.reset();
event_system::instance().stop();
}
};
using file_db_types = ::testing::Types<rdb_file_db, sqlite_file_db>;
template <typename db_t> std::unique_ptr<app_config> file_db_test<db_t>::config;
template <typename db_t> console_consumer file_db_test<db_t>::console_;
template <typename db_t> std::unique_ptr<db_t> file_db_test<db_t>::file_db;
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_FILE_DB_FIXTURE_HPP

View File

@@ -0,0 +1,72 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_FILE_MGR_DB_FIXTURE_HPP
#define REPERTORY_TEST_INCLUDE_FIXTURES_FILE_MGR_DB_FIXTURE_HPP
#include "test_common.hpp"
#include "app_config.hpp"
#include "db/impl/rdb_file_mgr_db.hpp"
#include "db/impl/sqlite_file_mgr_db.hpp"
#include "events/consumers/console_consumer.hpp"
#include "events/event_system.hpp"
namespace repertory {
template <typename db_t> class file_mgr_db_test : public ::testing::Test {
protected:
static std::unique_ptr<app_config> config;
static console_consumer console_;
static std::unique_ptr<db_t> file_mgr_db;
protected:
static void SetUpTestCase() {
static std::uint64_t idx{};
event_system::instance().start();
auto cfg_directory = utils::path::combine(test::get_test_output_dir(),
{
"file_mgr_db_test",
std::to_string(++idx),
});
config = std::make_unique<app_config>(provider_type::s3, cfg_directory);
file_mgr_db = std::make_unique<db_t>(*config);
}
static void TearDownTestCase() {
file_mgr_db.reset();
config.reset();
event_system::instance().stop();
}
};
using file_mgr_db_types = ::testing::Types<rdb_file_mgr_db, sqlite_file_mgr_db>;
template <typename db_t>
std::unique_ptr<app_config> file_mgr_db_test<db_t>::config;
template <typename db_t> console_consumer file_mgr_db_test<db_t>::console_;
template <typename db_t>
std::unique_ptr<db_t> file_mgr_db_test<db_t>::file_mgr_db;
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_FILE_MGR_DB_FIXTURE_HPP

View File

@@ -0,0 +1,415 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_FUSE_FIXTURE_HPP
#define REPERTORY_TEST_INCLUDE_FIXTURES_FUSE_FIXTURE_HPP
#if !defined(_WIN32)
#include "test_common.hpp"
#include "app_config.hpp"
#include "comm/curl/curl_comm.hpp"
#include "db/i_meta_db.hpp"
#include "db/meta_db.hpp"
#include "drives/fuse/fuse_drive.hpp"
#include "platform/platform.hpp"
#include "providers/encrypt/encrypt_provider.hpp"
#include "providers/s3/s3_provider.hpp"
#include "providers/sia/sia_provider.hpp"
#include "types/repertory.hpp"
#include "utils/event_capture.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
#include "utils/utils.hpp"
#if !defined(ACCESSPERMS)
#define ACCESSPERMS (S_IRWXU | S_IRWXG | S_IRWXO) /* 0777 */
#endif
namespace {
std::atomic<std::size_t> idx{0U};
constexpr const auto SLEEP_SECONDS{1.5s};
} // namespace
namespace repertory {
struct local_s3 final {
static constexpr const provider_type type{provider_type::s3};
static constexpr const provider_type type2{provider_type::s3};
};
struct local_sia final {
static constexpr const provider_type type{provider_type::sia};
static constexpr const provider_type type2{provider_type::sia};
};
struct remote_s3 final {
static constexpr const provider_type type{provider_type::remote};
static constexpr const provider_type type2{provider_type::s3};
};
struct remote_sia final {
static constexpr const provider_type type{provider_type::remote};
static constexpr const provider_type type2{provider_type::sia};
};
template <typename provider_t> class fuse_test : public ::testing::Test {
public:
static std::unique_ptr<app_config> config;
static std::filesystem::path current_directory;
static provider_type current_provider;
static std::vector<std::string> drive_args;
static std::vector<std::string> drive_args2;
static std::unique_ptr<i_meta_db> meta;
static std::string mount_location;
static std::string mount_location2;
protected:
static void SetUpTestCase() {
current_directory = std::filesystem::current_path();
const auto mount_s3 = [&]() {
{
auto test_directory = utils::path::combine(
test::get_test_output_dir(),
{
"fuse_test",
app_config::get_provider_name(current_provider),
});
mount_location = utils::path::combine(test_directory, {"mount"});
ASSERT_TRUE(utils::file::directory(mount_location).create_directory());
auto cfg_directory = utils::path::combine(test_directory, {"cfg"});
ASSERT_TRUE(utils::file::directory(cfg_directory).create_directory());
config = std::make_unique<app_config>(current_provider, cfg_directory);
{
app_config src_cfg{
provider_type::s3,
utils::path::combine(test::get_test_config_dir(), {"s3"}),
};
config->set_enable_drive_events(true);
config->set_event_level(event_level::trace);
config->set_s3_config(src_cfg.get_s3_config());
auto r_cfg = config->get_remote_mount();
r_cfg.enable = true;
r_cfg.api_port = 30000U;
config->set_remote_mount(r_cfg);
}
drive_args = std::vector<std::string>({
"-dd",
config->get_data_directory(),
"-s3",
"-na",
"s3",
mount_location,
});
}
meta = create_meta_db(*config);
execute_mount(drive_args, mount_location);
};
const auto mount_sia = [&]() {
{
auto test_directory = utils::path::combine(
test::get_test_output_dir(),
{
"fuse_test",
app_config::get_provider_name(current_provider),
});
mount_location = utils::path::combine(test_directory, {"mount"});
ASSERT_TRUE(utils::file::directory(mount_location).create_directory());
auto cfg_directory = utils::path::combine(test_directory, {"cfg"});
ASSERT_TRUE(utils::file::directory(cfg_directory).create_directory());
config = std::make_unique<app_config>(current_provider, cfg_directory);
{
app_config src_cfg{
provider_type::sia,
utils::path::combine(test::get_test_config_dir(), {"sia"}),
};
config->set_enable_drive_events(true);
config->set_event_level(event_level::trace);
config->set_host_config(src_cfg.get_host_config());
config->set_sia_config(src_cfg.get_sia_config());
auto r_cfg = config->get_remote_mount();
r_cfg.enable = true;
r_cfg.api_port = 30000U;
config->set_remote_mount(r_cfg);
}
drive_args = std::vector<std::string>({
"-dd",
config->get_data_directory(),
"-na",
"sia",
mount_location,
});
}
meta = create_meta_db(*config);
execute_mount(drive_args, mount_location);
};
const auto mount_remote = [&]() {
{
mount_location2 = mount_location;
auto test_directory = utils::path::combine(
test::get_test_output_dir(),
{
"fuse_test",
app_config::get_provider_name(provider_t::type) + '_' +
app_config::get_provider_name(provider_t::type2),
});
mount_location = utils::path::combine(test_directory, {"mount"});
ASSERT_TRUE(utils::file::directory(mount_location).create_directory());
auto cfg_directory = utils::path::combine(test_directory, {"cfg"});
ASSERT_TRUE(utils::file::directory(cfg_directory).create_directory());
auto config2 =
std::make_unique<app_config>(provider_type::remote, cfg_directory);
config2->set_enable_drive_events(true);
config2->set_event_level(event_level::trace);
drive_args2 = std::vector<std::string>({
"-dd",
config2->get_data_directory(),
"-rm",
"localhost:30000",
mount_location,
});
}
execute_mount(drive_args2, mount_location);
};
switch (provider_t::type) {
case provider_type::s3: {
mount_s3();
} break;
case provider_type::sia: {
mount_sia();
} break;
case provider_type::remote: {
switch (provider_t::type2) {
case provider_type::s3: {
mount_s3();
} break;
case provider_type::sia: {
mount_sia();
} break;
default:
throw std::runtime_error("remote provider type is not implemented");
return;
}
mount_remote();
} break;
default:
throw std::runtime_error("provider type is not implemented");
return;
}
}
static void TearDownTestCase() {
if (provider_t::type == provider_type::remote) {
execute_unmount(mount_location);
execute_unmount(mount_location2);
} else {
execute_unmount(mount_location);
}
meta.reset();
config.reset();
std::filesystem::current_path(current_directory);
}
public:
static auto create_file_path(std::string &file_name) {
file_name += std::to_string(++idx);
auto file_path = utils::path::combine(mount_location, {file_name});
return file_path;
}
static auto create_file_and_test(std::string &file_name, mode_t perms)
-> std::string {
file_name += std::to_string(++idx);
auto file_path = utils::path::combine(mount_location, {file_name});
auto handle = open(file_path.c_str(), O_CREAT | O_EXCL | O_RDWR, perms);
EXPECT_LE(1, handle);
auto opt_size = utils::file::file{file_path}.size();
EXPECT_TRUE(opt_size.has_value());
if (opt_size.has_value()) {
EXPECT_EQ(0U, opt_size.value());
}
EXPECT_EQ(0, close(handle));
EXPECT_TRUE(utils::file::file(file_path).exists());
EXPECT_FALSE(utils::file::directory(file_path).exists());
struct stat64 unix_st{};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(getgid(), unix_st.st_gid);
EXPECT_EQ(getuid(), unix_st.st_uid);
return file_path;
}
static auto create_file_and_test(std::string &file_name) -> std::string {
return create_file_and_test(file_name, ACCESSPERMS);
}
static auto create_directory_and_test(std::string &dir_name, mode_t perms)
-> std::string {
dir_name += std::to_string(++idx);
auto dir_path = utils::path::combine(mount_location, {dir_name});
mkdir(dir_path.c_str(), perms);
EXPECT_TRUE(utils::file::directory(dir_path).exists());
EXPECT_FALSE(utils::file::file(dir_path).exists());
struct stat64 unix_st{};
EXPECT_EQ(0, stat64(dir_path.c_str(), &unix_st));
EXPECT_EQ(getgid(), unix_st.st_gid);
EXPECT_EQ(getuid(), unix_st.st_uid);
return dir_path;
}
static auto create_directory_and_test(std::string &dir_name) -> std::string {
return create_directory_and_test(dir_name, ACCESSPERMS);
}
static auto create_root_file(std::string &file_name) -> std::string {
auto file_path = create_file_and_test(file_name);
auto api_path = utils::path::create_api_path(file_name);
[[maybe_unused]] auto res =
meta->set_item_meta(api_path, {
{META_UID, "0"},
{META_GID, "0"},
});
std::this_thread::sleep_for(SLEEP_SECONDS);
return file_path;
}
static void execute_mount(auto args, auto location) {
auto mount_cmd = "./repertory " + utils::string::join(args, ' ');
std::cout << "mount command: " << mount_cmd << std::endl;
ASSERT_EQ(0, system(mount_cmd.c_str()));
std::this_thread::sleep_for(5s);
ASSERT_TRUE(utils::file::directory{location}.exists());
}
static void execute_unmount(auto location) {
auto unmounted{false};
for (int i = 0; not unmounted && (i < 50); i++) {
auto res = fuse_base::unmount(location);
unmounted = res == 0;
ASSERT_EQ(0, res);
if (not unmounted) {
std::this_thread::sleep_for(5s);
}
}
EXPECT_TRUE(unmounted);
}
static void rmdir_and_test(std::string_view dir_path) {
EXPECT_TRUE(utils::file::directory(dir_path).remove());
EXPECT_FALSE(utils::file::directory(dir_path).exists());
EXPECT_FALSE(utils::file::file(dir_path).exists());
}
static void unlink_file_and_test(std::string_view file_path) {
EXPECT_TRUE(utils::file::file(file_path).remove());
EXPECT_FALSE(utils::file::file(file_path).exists());
EXPECT_FALSE(utils::file::directory(file_path).exists());
}
static void unlink_root_file(const std::string &file_path) {
auto api_path = utils::path::create_api_path(
utils::path::strip_to_file_name(file_path));
[[maybe_unused]] auto res =
meta->set_item_meta(api_path, {
{META_UID, std::to_string(getuid())},
{META_GID, std::to_string(getgid())},
});
std::this_thread::sleep_for(SLEEP_SECONDS);
unlink_file_and_test(file_path);
}
};
template <typename provider_t>
std::unique_ptr<app_config> fuse_test<provider_t>::config;
template <typename provider_t>
std::filesystem::path fuse_test<provider_t>::current_directory;
template <typename provider_t>
provider_type fuse_test<provider_t>::current_provider{provider_t::type2};
template <typename provider_t>
std::vector<std::string> fuse_test<provider_t>::drive_args;
template <typename provider_t>
std::vector<std::string> fuse_test<provider_t>::drive_args2;
template <typename provider_t>
std::unique_ptr<i_meta_db> fuse_test<provider_t>::meta{};
template <typename provider_t>
std::string fuse_test<provider_t>::mount_location;
template <typename provider_t>
std::string fuse_test<provider_t>::mount_location2;
using fuse_provider_types = ::testing::Types<local_s3, remote_s3>;
// using fuse_provider_types =
// ::testing::Types<local_s3, remote_s3, local_sia, remote_sia>;
} // namespace repertory
#endif // !defined(_WIN32)
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_FUSE_FIXTURE_HPP

View File

@@ -0,0 +1,70 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_META_DB_FIXTURE_HPP
#define REPERTORY_TEST_INCLUDE_FIXTURES_META_DB_FIXTURE_HPP
#include "test_common.hpp"
#include "app_config.hpp"
#include "db/impl/rdb_meta_db.hpp"
#include "db/impl/sqlite_meta_db.hpp"
#include "events/consumers/console_consumer.hpp"
#include "events/event_system.hpp"
namespace repertory {
template <typename db_t> class meta_db_test : public ::testing::Test {
protected:
static std::unique_ptr<app_config> config;
static console_consumer console_;
static std::unique_ptr<db_t> meta_db;
protected:
static void SetUpTestCase() {
static std::uint64_t idx{};
event_system::instance().start();
auto cfg_directory = utils::path::combine(test::get_test_output_dir(),
{
"meta_db_test",
std::to_string(++idx),
});
config = std::make_unique<app_config>(provider_type::s3, cfg_directory);
meta_db = std::make_unique<db_t>(*config);
}
static void TearDownTestCase() {
meta_db.reset();
config.reset();
event_system::instance().stop();
}
};
using meta_db_types = ::testing::Types<rdb_meta_db, sqlite_meta_db>;
template <typename db_t> std::unique_ptr<app_config> meta_db_test<db_t>::config;
template <typename db_t> console_consumer meta_db_test<db_t>::console_;
template <typename db_t> std::unique_ptr<db_t> meta_db_test<db_t>::meta_db;
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_META_DB_FIXTURE_HPP

View File

@@ -0,0 +1,289 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
#define REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP
#if defined(_WIN32)
#include "test_common.hpp"
#include "app_config.hpp"
#include "drives/winfsp/remotewinfsp/remote_winfsp_drive.hpp"
#include "drives/winfsp/winfsp_drive.hpp"
#include "platform/platform.hpp"
#include "providers/i_provider.hpp"
#include "providers/s3/s3_provider.hpp"
#include "providers/sia/sia_provider.hpp"
#include "types/repertory.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
namespace {
std::atomic<std::size_t> idx{0U};
constexpr const auto SLEEP_SECONDS{1.5s};
} // namespace
namespace repertory {
struct local_s3 final {
static constexpr const provider_type type{provider_type::s3};
static constexpr const provider_type type2{provider_type::s3};
};
struct local_sia final {
static constexpr const provider_type type{provider_type::sia};
static constexpr const provider_type type2{provider_type::sia};
};
struct remote_s3 final {
static constexpr const provider_type type{provider_type::remote};
static constexpr const provider_type type2{provider_type::s3};
};
struct remote_sia final {
static constexpr const provider_type type{provider_type::remote};
static constexpr const provider_type type2{provider_type::sia};
};
template <typename provider_t> class winfsp_test : public ::testing::Test {
public:
static std::filesystem::path current_directory;
static provider_type current_provider;
static std::vector<std::string> drive_args;
static std::vector<std::string> drive_args2;
static std::string mount_location;
static std::string mount_location2;
protected:
static void SetUpTestCase() {
current_directory = std::filesystem::current_path();
mount_location = utils::string::to_lower(std::string{"U:"});
const auto mount_s3 = [&]() {
{
auto test_directory = utils::path::combine(
test::get_test_output_dir(),
{
"winfsp_test",
app_config::get_provider_name(provider_type::s3),
});
auto cfg_directory = utils::path::combine(test_directory, {"cfg"});
ASSERT_TRUE(utils::file::directory(cfg_directory).create_directory());
auto config =
std::make_unique<app_config>(provider_type::s3, cfg_directory);
{
app_config src_cfg{
provider_type::s3,
utils::path::combine(test::get_test_config_dir(), {"s3"}),
};
config->set_enable_drive_events(true);
config->set_event_level(event_level::trace);
config->set_s3_config(src_cfg.get_s3_config());
auto r_cfg = config->get_remote_mount();
r_cfg.enable = true;
r_cfg.api_port = 30000U;
config->set_remote_mount(r_cfg);
}
drive_args = std::vector<std::string>({
"-dd",
config->get_data_directory(),
"-s3",
"-na",
"s3",
mount_location,
});
}
execute_mount(drive_args, mount_location);
};
const auto mount_sia = [&]() {
{
auto test_directory = utils::path::combine(
test::get_test_output_dir(),
{
"winfsp_test",
app_config::get_provider_name(provider_type::sia),
});
auto cfg_directory = utils::path::combine(test_directory, {"cfg"});
ASSERT_TRUE(utils::file::directory(cfg_directory).create_directory());
auto config =
std::make_unique<app_config>(provider_type::s3, cfg_directory);
{
app_config src_cfg{
provider_type::sia,
utils::path::combine(test::get_test_config_dir(), {"sia"}),
};
config->set_enable_drive_events(true);
config->set_event_level(event_level::trace);
config->set_host_config(src_cfg.get_host_config());
config->set_sia_config(src_cfg.get_sia_config());
auto r_cfg = config->get_remote_mount();
r_cfg.enable = true;
r_cfg.api_port = 30000U;
config->set_remote_mount(r_cfg);
}
drive_args = std::vector<std::string>({
"-dd",
config->get_data_directory(),
"-na",
"sia",
mount_location,
});
}
execute_mount(drive_args, mount_location);
};
const auto mount_remote = [&]() {
{
auto test_directory = utils::path::combine(
test::get_test_output_dir(),
{
"winfsp_test",
app_config::get_provider_name(provider_type::remote),
});
mount_location2 = mount_location;
mount_location = utils::string::to_lower(std::string{"V:"});
auto cfg_directory = utils::path::combine(test_directory, {"cfg2"});
ASSERT_TRUE(utils::file::directory(cfg_directory).create_directory());
auto config =
std::make_unique<app_config>(provider_type::remote, cfg_directory);
config->set_enable_drive_events(true);
config->set_event_level(event_level::trace);
drive_args2 = std::vector<std::string>({
"-dd",
config->get_data_directory(),
"-rm",
"localhost:30000",
mount_location,
});
}
execute_mount(drive_args2, mount_location);
};
switch (provider_t::type) {
case provider_type::s3: {
mount_s3();
} break;
case provider_type::sia: {
mount_sia();
} break;
case provider_type::remote: {
switch (provider_t::type2) {
case provider_type::s3: {
mount_s3();
} break;
case provider_type::sia: {
mount_sia();
} break;
default:
throw std::runtime_error("remote provider type is not implemented");
return;
}
mount_remote();
} break;
default:
throw std::runtime_error("provider type is not implemented");
return;
}
}
static void TearDownTestCase() {
if (provider_t::type == provider_type::remote) {
execute_unmount(drive_args2, mount_location);
execute_unmount(drive_args, mount_location2);
} else {
execute_unmount(drive_args, mount_location);
}
std::filesystem::current_path(current_directory);
}
static void execute_mount(auto args, auto location) {
auto mount_cmd =
"start .\\repertory.exe -f " + utils::string::join(args, ' ');
std::cout << "mount command: " << mount_cmd << std::endl;
ASSERT_EQ(0, system(mount_cmd.c_str()));
std::this_thread::sleep_for(5s);
ASSERT_TRUE(utils::file::directory{location}.exists());
}
static void execute_unmount(auto args, auto location) {
auto unmounted{false};
auto unmount_cmd =
".\\repertory.exe " + utils::string::join(args, ' ') + " -unmount";
for (int i = 0; not unmounted && (i < 6); i++) {
std::cout << "unmount command: " << unmount_cmd << std::endl;
system(unmount_cmd.c_str());
unmounted = not utils::file::directory{location}.exists();
if (not unmounted) {
std::this_thread::sleep_for(5s);
}
}
ASSERT_TRUE(unmounted);
}
};
template <typename provider_t>
std::filesystem::path winfsp_test<provider_t>::current_directory;
template <typename provider_t>
provider_type winfsp_test<provider_t>::current_provider{provider_t::type2};
template <typename provider_t>
std::vector<std::string> winfsp_test<provider_t>::drive_args;
template <typename provider_t>
std::vector<std::string> winfsp_test<provider_t>::drive_args2;
template <typename provider_t>
std::string winfsp_test<provider_t>::mount_location;
template <typename provider_t>
std::string winfsp_test<provider_t>::mount_location2;
// using winfsp_provider_types = ::testing::Types<local_s3, remote_s3,
// local_sia, remote_sia>;
using winfsp_provider_types = ::testing::Types<local_s3, remote_s3>;
} // namespace repertory
#endif // defined(_WIN32)
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_WINFSP_FIXTURE_HPP

View File

@@ -0,0 +1,159 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_FIXTURES_MOCKS_MOCK_FUSE_DRIVE_HPP_
#define REPERTORY_TEST_INCLUDE_FIXTURES_MOCKS_MOCK_FUSE_DRIVE_HPP_
#if !defined(_WIN32)
#include "test_common.hpp"
#include "drives/fuse/i_fuse_drive.hpp"
#include "types/remote.hpp"
#include "types/repertory.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
#include "utils/time.hpp"
#include "utils/utils.hpp"
namespace repertory {
class mock_fuse_drive final : public virtual i_fuse_drive {
public:
explicit mock_fuse_drive(std::string mount_location)
: mount_location_(std::move(mount_location)) {}
private:
std::string mount_location_;
std::unordered_map<std::string, api_meta_map> meta_;
public:
auto check_owner(const std::string &) const -> api_error override {
return api_error::success;
}
auto check_parent_access(const std::string &,
int) const -> api_error override {
return api_error::success;
}
auto get_directory_item_count(const std::string &) const
-> std::uint64_t override {
return 1;
}
auto get_directory_items(const std::string &) const
-> directory_item_list override {
directory_item_list list{};
directory_item dir_item{};
dir_item.api_path = ".";
dir_item.directory = true;
dir_item.size = 0;
dir_item.meta = {
{META_ATTRIBUTES, "16"},
{META_MODIFIED, std::to_string(utils::time::get_time_now())},
{META_WRITTEN, std::to_string(utils::time::get_time_now())},
{META_ACCESSED, std::to_string(utils::time::get_time_now())},
{META_CREATION, std::to_string(utils::time::get_time_now())}};
list.emplace_back(dir_item);
dir_item.api_path = "..";
list.emplace_back(dir_item);
return list;
}
auto get_file_size(const std::string &) const -> std::uint64_t override {
return 0U;
}
auto get_item_meta(const std::string &api_path,
api_meta_map &meta) const -> api_error override {
meta = const_cast<mock_fuse_drive *>(this)->meta_[api_path];
return api_error::success;
}
auto get_item_meta(const std::string &api_path, const std::string &name,
std::string &value) const -> api_error override {
value = const_cast<mock_fuse_drive *>(this)->meta_[api_path][name];
if (value.empty()) {
value = "0";
}
return api_error::success;
}
auto get_total_drive_space() const -> std::uint64_t override {
return 100ULL * 1024ULL * 1024ULL;
}
auto get_total_item_count() const -> std::uint64_t override { return 0U; }
auto get_used_drive_space() const -> std::uint64_t override { return 0U; }
void get_volume_info(UINT64 &total_size, UINT64 &free_size,
std::string &volume_label) const override {
free_size = 100u;
total_size = 200u;
volume_label = "TestVolumeLabel";
}
auto rename_directory(const std::string &from_api_path,
const std::string &to_api_path) -> int override {
const auto from_file_path =
utils::path::combine(mount_location_, {from_api_path});
const auto to_file_path =
utils::path::combine(mount_location_, {to_api_path});
return rename(from_file_path.c_str(), to_file_path.c_str());
}
auto rename_file(const std::string &from_api_path,
const std::string &to_api_path,
bool overwrite) -> int override {
const auto from_file_path =
utils::path::combine(mount_location_, {from_api_path});
const auto to_file_path =
utils::path::combine(mount_location_, {to_api_path});
if (overwrite) {
if (not utils::file::file(to_file_path).remove()) {
return -1;
}
} else if (utils::file::directory(to_file_path).exists() ||
utils::file::file(to_file_path).exists()) {
errno = EEXIST;
return -1;
}
return rename(from_file_path.c_str(), to_file_path.c_str());
}
auto is_processing(const std::string &) const -> bool override {
return false;
}
void set_item_meta(const std::string &api_path, const std::string &key,
const std::string &value) override {
meta_[api_path][key] = value;
}
};
} // namespace repertory
#endif // !defined(_WIN32)
#endif // REPERTORY_TEST_INCLUDE_FIXTURES_MOCKS_MOCK_FUSE_DRIVE_HPP_

View File

@@ -0,0 +1,108 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_MOCKS_MOCK_OPEN_FILE_HPP_
#define REPERTORY_TEST_INCLUDE_MOCKS_MOCK_OPEN_FILE_HPP_
#include "test_common.hpp"
#include "file_manager/i_open_file.hpp"
namespace repertory {
class mock_open_file : public virtual i_closeable_open_file {
public:
MOCK_METHOD(void, add, (std::uint64_t handle, open_file_data ofd),
(override));
MOCK_METHOD(bool, can_close, (), (const, override));
MOCK_METHOD(bool, close, (), (override));
MOCK_METHOD(std::string, get_api_path, (), (const, override));
MOCK_METHOD(std::size_t, get_chunk_size, (), (const, override));
MOCK_METHOD(std::uint64_t, get_file_size, (), (const, override));
MOCK_METHOD(filesystem_item, get_filesystem_item, (), (const, override));
MOCK_METHOD(open_file_data &, get_open_data, (std::uint64_t handle),
(override));
MOCK_METHOD(const open_file_data &, get_open_data, (std::uint64_t handle),
(const, override));
MOCK_METHOD(std::size_t, get_open_file_count, (), (const, override));
MOCK_METHOD(boost::dynamic_bitset<>, get_read_state, (), (const, override));
MOCK_METHOD(bool, get_allocated, (), (const, override));
MOCK_METHOD(std::vector<std::uint64_t>, get_handles, (), (const, override));
MOCK_METHOD((std::map<std::uint64_t, open_file_data> &), get_open_data, (),
(override));
MOCK_METHOD((const std::map<std::uint64_t, open_file_data> &), get_open_data,
(), (const, override));
MOCK_METHOD(bool, get_read_state, (std::size_t chunk), (const, override));
MOCK_METHOD(std::string, get_source_path, (), (const, override));
MOCK_METHOD(bool, has_handle, (std::uint64_t handle), (const, override));
MOCK_METHOD(bool, is_complete, (), (const, override));
MOCK_METHOD(bool, is_directory, (), (const, override));
MOCK_METHOD(bool, is_modified, (), (const, override));
MOCK_METHOD(bool, is_write_supported, (), (const, override));
MOCK_METHOD(api_error, native_operation, (native_operation_callback callback),
(override));
MOCK_METHOD(api_error, native_operation,
(std::uint64_t new_file_size, native_operation_callback callback),
(override));
MOCK_METHOD(api_error, read,
(std::size_t read_size, std::uint64_t read_offset,
data_buffer &data),
(override));
MOCK_METHOD(void, remove, (std::uint64_t handle), (override));
MOCK_METHOD(void, remove_all, (), (override));
MOCK_METHOD(api_error, resize, (std::uint64_t new_file_size), (override));
MOCK_METHOD(void, set_api_path, (const std::string &api_path), (override));
MOCK_METHOD(api_error, write,
(std::uint64_t write_offset, const data_buffer &data,
std::size_t &bytes_written),
(override));
};
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_MOCKS_MOCK_OPEN_FILE_HPP_

View File

@@ -0,0 +1,162 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_MOCKS_MOCK_PROVIDER_HPP_
#define REPERTORY_TEST_INCLUDE_MOCKS_MOCK_PROVIDER_HPP_
#include "test_common.hpp"
#include "providers/i_provider.hpp"
#include "types/repertory.hpp"
namespace repertory {
class mock_provider : public virtual i_provider {
public:
mock_provider(bool allow_rename = true) : allow_rename_(allow_rename) {}
private:
const bool allow_rename_;
public:
MOCK_METHOD(api_error, create_directory,
(const std::string &api_path, api_meta_map &meta), (override));
MOCK_METHOD(api_error, create_directory_clone_source_meta,
(const std::string &source_api_path, const std::string &api_path),
(override));
MOCK_METHOD(api_error, create_file,
(const std::string &api_path, api_meta_map &meta), (override));
MOCK_METHOD(api_error, get_api_path_from_source,
(const std::string &source_path, std::string &api_path),
(const, override));
MOCK_METHOD(std::uint64_t, get_directory_item_count,
(const std::string &api_path), (const, override));
MOCK_METHOD(api_error, get_directory_items,
(const std::string &api_path, directory_item_list &list),
(const, override));
MOCK_METHOD(api_error, get_file,
(const std::string &api_path, api_file &file), (const, override));
MOCK_METHOD(api_error, get_file_list,
(api_file_list & list, std::string &marker), (const, override));
MOCK_METHOD(api_error, get_file_size,
(const std::string &api_path, std::uint64_t &file_size),
(const, override));
MOCK_METHOD(api_error, get_filesystem_item,
(const std::string &api_path, bool directory,
filesystem_item &fsi),
(const, override));
MOCK_METHOD(api_error, get_filesystem_item_and_file,
(const std::string &api_path, api_file &file,
filesystem_item &fsi),
(const, override));
MOCK_METHOD(api_error, get_filesystem_item_from_source_path,
(const std::string &source_path, filesystem_item &fsi),
(const, override));
MOCK_METHOD(api_error, get_item_meta,
(const std::string &api_path, api_meta_map &meta),
(const, override));
MOCK_METHOD(api_error, get_item_meta,
(const std::string &api_path, const std::string &key,
std::string &value),
(const, override));
MOCK_METHOD((std::vector<std::string>), get_pinned_files, (),
(const, override));
MOCK_METHOD(provider_type, get_provider_type, (), (const, override));
MOCK_METHOD(std::uint64_t, get_total_drive_space, (), (const, override));
MOCK_METHOD(std::uint64_t, get_total_item_count, (), (const, override));
MOCK_METHOD(std::uint64_t, get_used_drive_space, (), (const, override));
MOCK_METHOD(bool, is_read_only, (), (const, override));
MOCK_METHOD(api_error, is_directory,
(const std::string &api_path, bool &exists), (const, override));
MOCK_METHOD(api_error, is_file, (const std::string &api_path, bool &exists),
(const, override));
bool is_file_writeable(const std::string & /* api_path */) const override {
return true;
}
MOCK_METHOD(bool, is_online, (), (const, override));
bool is_rename_supported() const override { return allow_rename_; }
MOCK_METHOD(api_error, read_file_bytes,
(const std::string &path, std::size_t size, std::uint64_t offset,
data_buffer &data, stop_type &stop_requested),
(override));
MOCK_METHOD(api_error, remove_directory, (const std::string &api_path),
(override));
MOCK_METHOD(api_error, remove_file, (const std::string &api_path),
(override));
MOCK_METHOD(api_error, remove_item_meta,
(const std::string &api_path, const std::string &key),
(override));
MOCK_METHOD(api_error, rename_file,
(const std::string &from_api_path,
const std::string &to_api_path),
(override));
MOCK_METHOD(api_error, set_item_meta,
(const std::string &api_path, const std::string &key,
const std::string &value),
(override));
MOCK_METHOD(api_error, set_item_meta,
(const std::string &api_path, const api_meta_map &meta),
(override));
MOCK_METHOD(bool, start,
(api_item_added_callback api_item_added, i_file_manager *fm),
(override));
MOCK_METHOD(void, stop, (), (override));
MOCK_METHOD(api_error, upload_file,
(const std::string &api_path, const std::string &source_path,
stop_type &stop_requested),
(override));
};
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_MOCKS_MOCK_PROVIDER_HPP_

View File

@@ -0,0 +1,44 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_MOCKS_MOCK_UPLOAD_MANAGER_HPP_
#define REPERTORY_TEST_INCLUDE_MOCKS_MOCK_UPLOAD_MANAGER_HPP_
#include "test_common.hpp"
#include "file_manager/i_upload_manager.hpp"
namespace repertory {
class mock_upload_manager : public i_upload_manager {
public:
MOCK_METHOD(void, queue_upload, (const i_open_file &o), (override));
MOCK_METHOD(void, remove_resume,
(const std::string &api_path, const std::string &source_path),
(override));
MOCK_METHOD(void, remove_upload, (const std::string &api_path), (override));
MOCK_METHOD(void, store_resume, (const i_open_file &o), (override));
};
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_MOCKS_MOCK_UPLOAD_MANAGER_HPP_

View File

@@ -0,0 +1,182 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_MOCKS_MOCK_WINFSP_DRIVE_HPP_
#define REPERTORY_TEST_INCLUDE_MOCKS_MOCK_WINFSP_DRIVE_HPP_
#if defined(_WIN32)
#include "test_common.hpp"
#include "drives/winfsp/i_winfsp_drive.hpp"
#include "utils/common.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
#include "utils/time.hpp"
#include "utils/utils.hpp"
namespace repertory {
class mock_winfsp_drive final : public virtual i_winfsp_drive {
public:
explicit mock_winfsp_drive(std::string mount_location)
: mount_location_(std::move(mount_location)) {}
private:
std::string mount_location_;
public:
[[nodiscard]] auto
get_directory_item_count(const std::string & /*api_path*/) const
-> std::uint64_t override {
return 1;
}
[[nodiscard]] auto get_directory_items(const std::string & /*api_path*/) const
-> directory_item_list override {
directory_item_list list{};
directory_item di{};
di.api_path = ".";
di.directory = true;
di.size = 0U;
di.meta = {{META_ATTRIBUTES, "16"},
{META_MODIFIED, std::to_string(utils::time::get_time_now())},
{META_WRITTEN, std::to_string(utils::time::get_time_now())},
{META_ACCESSED, std::to_string(utils::time::get_time_now())},
{META_CREATION, std::to_string(utils::time::get_time_now())}};
list.emplace_back(di);
di.api_path = "..";
list.emplace_back(di);
return list;
}
[[nodiscard]] auto get_file_size(const std::string & /*api_path*/) const
-> std::uint64_t override {
return 0;
}
auto get_item_meta(const std::string & /*api_path*/,
api_meta_map & /* meta */) const -> api_error override {
return api_error::error;
}
auto get_item_meta(const std::string & /*api_path*/,
const std::string & /*name*/,
std::string & /*value*/) const -> api_error override {
return api_error::error;
}
auto get_security_by_name(PWSTR /*file_name*/, PUINT32 attributes,
PSECURITY_DESCRIPTOR descriptor,
std::uint64_t *descriptor_size)
-> NTSTATUS override {
auto ret = STATUS_SUCCESS;
if (attributes != nullptr) {
*attributes = FILE_ATTRIBUTE_NORMAL;
}
if (descriptor_size) {
ULONG sz{0U};
PSECURITY_DESCRIPTOR sd{nullptr};
if (::ConvertStringSecurityDescriptorToSecurityDescriptor(
"O:BAG:BAD:P(A;;FA;;;SY)(A;;FA;;;BA)(A;;FA;;;WD)",
SDDL_REVISION_1, &sd, &sz)) {
if (sz > *descriptor_size) {
ret = STATUS_BUFFER_TOO_SMALL;
} else {
::CopyMemory(descriptor, sd, sz);
}
*descriptor_size = sz;
::LocalFree(sd);
} else {
ret = FspNtStatusFromWin32(::GetLastError());
}
}
return ret;
}
[[nodiscard]] auto get_total_drive_space() const -> std::uint64_t override {
return 100ULL * 1024ULL * 1024ULL;
}
[[nodiscard]] auto get_total_item_count() const -> std::uint64_t override {
return 0U;
}
[[nodiscard]] auto get_used_drive_space() const -> std::uint64_t override {
return 0U;
}
void get_volume_info(UINT64 &total_size, UINT64 &free_size,
std::string &volume_label) const override {
free_size = 100U;
total_size = 200U;
volume_label = "TestVolumeLabel";
}
auto populate_file_info(const std::string &api_path,
remote::file_info &file_info) const
-> api_error override {
auto file_path = utils::path::combine(mount_location_, {api_path});
auto directory = utils::file::directory(file_path).exists();
auto attributes =
FILE_FLAG_BACKUP_SEMANTICS |
(directory ? FILE_ATTRIBUTE_DIRECTORY : FILE_ATTRIBUTE_ARCHIVE);
auto share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
auto handle = ::CreateFileA(
file_path.c_str(), GENERIC_READ, static_cast<DWORD>(share_mode),
nullptr, OPEN_EXISTING, static_cast<DWORD>(attributes), nullptr);
FILE_BASIC_INFO basic_info{};
::GetFileInformationByHandleEx(handle, FileBasicInfo, &basic_info,
sizeof(basic_info));
if (not directory) {
auto opt_size = utils::file::file{file_path}.size();
if (not opt_size.has_value()) {
return api_error::os_error;
}
file_info.FileSize = opt_size.value();
}
file_info.AllocationSize =
directory ? 0
: utils::divide_with_ceiling(file_info.FileSize,
WINFSP_ALLOCATION_UNIT) *
WINFSP_ALLOCATION_UNIT;
file_info.FileAttributes = basic_info.FileAttributes;
file_info.ChangeTime = static_cast<UINT64>(basic_info.ChangeTime.QuadPart);
file_info.CreationTime =
static_cast<UINT64>(basic_info.CreationTime.QuadPart);
file_info.LastAccessTime =
static_cast<UINT64>(basic_info.LastAccessTime.QuadPart);
file_info.LastWriteTime =
static_cast<UINT64>(basic_info.LastWriteTime.QuadPart);
::CloseHandle(handle);
return api_error::success;
}
};
} // namespace repertory
#endif // _WIN32
#endif // REPERTORY_TEST_INCLUDE_MOCKS_MOCK_WINFSP_DRIVE_HPP_

View File

@@ -0,0 +1,33 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_TEST_COMMON_HPP_
#define REPERTORY_TEST_INCLUDE_TEST_COMMON_HPP_
REPERTORY_IGNORE_WARNINGS_ENABLE()
#include "test.hpp"
REPERTORY_IGNORE_WARNINGS_DISABLE()
#include "events/consumers/console_consumer.hpp"
#include "events/event_system.hpp"
#include "events/events.hpp"
#endif // REPERTORY_TEST_INCLUDE_TEST_COMMON_HPP_

View File

@@ -0,0 +1,109 @@
/*
Copyright <2018-2024> <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.
*/
#ifndef REPERTORY_TEST_INCLUDE_UTILS_EVENT_CAPTURE_HPP_
#define REPERTORY_TEST_INCLUDE_UTILS_EVENT_CAPTURE_HPP_
#include "test_common.hpp"
#include "utils/collection.hpp"
#include "utils/utils.hpp"
namespace repertory {
class event_capture final {
E_CONSUMER();
public:
explicit event_capture(std::vector<std::string> event_names,
std::vector<std::string> non_fired_event_names = {})
: event_names_(std::move(event_names)),
non_fired_event_names_(std::move(non_fired_event_names)) {
E_SUBSCRIBE_ALL(process_event);
}
~event_capture() {
wait_for_empty();
E_CONSUMER_RELEASE();
EXPECT_TRUE(event_names_.empty());
for (const auto &event_name : event_names_) {
std::cerr << '\t' << event_name << std::endl;
}
}
private:
std::vector<std::string> event_names_;
std::vector<std::string> fired_event_names_;
std::vector<std::string> non_fired_event_names_;
std::mutex mutex_;
std::condition_variable notify_;
public:
void process_event(const event &event) {
unique_mutex_lock l(mutex_);
utils::collection::remove_element(event_names_, event.get_name());
fired_event_names_.emplace_back(event.get_name());
notify_.notify_all();
l.unlock();
for (size_t i = 0; i < non_fired_event_names_.size(); i++) {
const auto it = std::find(non_fired_event_names_.begin(),
non_fired_event_names_.end(), event.get_name());
EXPECT_EQ(non_fired_event_names_.end(), it);
if (it != non_fired_event_names_.end()) {
std::cerr << '\t' << *it << std::endl;
}
}
}
void wait_for_empty() {
const auto start_time = std::chrono::system_clock::now();
unique_mutex_lock l(mutex_);
while (not event_names_.empty() &&
(std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now() - start_time)
.count() < 10)) {
notify_.wait_for(l, 1s);
}
l.unlock();
}
[[nodiscard]] auto wait_for_event(const std::string &event_name) -> bool {
auto missing = true;
const auto start_time = std::chrono::system_clock::now();
unique_mutex_lock l(mutex_);
while ((std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now() - start_time)
.count() < 10) &&
(missing =
(std::find(fired_event_names_.begin(), fired_event_names_.end(),
event_name) == fired_event_names_.end()))) {
notify_.wait_for(l, 1s);
}
l.unlock();
return not missing;
}
};
} // namespace repertory
#endif // REPERTORY_TEST_INCLUDE_UTILS_EVENT_CAPTURE_HPP_

View File

@@ -0,0 +1,49 @@
/*
Copyright <2018-2024> <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(PROJECT_ENABLE_BACKWARD_CPP)
#include "backward.hpp"
#endif // defined(PROJECT_ENABLE_BACKWARD_CPP)
#include "initialize.hpp"
#include "test_common.hpp"
using namespace repertory;
int PROJECT_TEST_RESULT{0};
auto main(int argc, char **argv) -> int {
#if defined(PROJECT_ENABLE_BACKWARD_CPP)
static backward::SignalHandling sh;
#endif // defined(PROJECT_ENABLE_BACKWARD_CPP)
if (not repertory::project_initialize()) {
std::cerr << "fatal: failed to initialize repertory" << std::endl;
return -1;
}
::testing::InitGoogleTest(&argc, argv);
PROJECT_TEST_RESULT = RUN_ALL_TESTS();
repertory::project_cleanup();
return PROJECT_TEST_RESULT;
}

View File

@@ -0,0 +1,68 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "types/repertory.hpp"
namespace repertory {
TEST(atomic, atomic_primitive) {
atomic<std::uint16_t> value;
value = 5U;
EXPECT_EQ(5U, static_cast<std::uint16_t>(value));
EXPECT_EQ(5U, value.load());
value.store(6U);
EXPECT_EQ(6U, static_cast<std::uint16_t>(value));
EXPECT_EQ(6U, value.load());
}
TEST(atomic, atomic_primitive_equality) {
atomic<std::uint16_t> value1{5U};
atomic<std::uint16_t> value2{5U};
EXPECT_EQ(value1, value1);
EXPECT_EQ(value2, value2);
EXPECT_EQ(value1, value2);
EXPECT_EQ(static_cast<std::uint16_t>(value1), 5U);
EXPECT_EQ(static_cast<std::uint16_t>(value2), 5U);
}
TEST(atomic, atomic_primitive_inequality) {
atomic<std::uint16_t> value1{5U};
atomic<std::uint16_t> value2{6U};
EXPECT_NE(value1, value2);
EXPECT_NE(static_cast<std::uint16_t>(value1), 6U);
EXPECT_NE(static_cast<std::uint16_t>(value2), 5U);
}
TEST(atomic, atomic_struct) {
atomic<encrypt_config> value{
encrypt_config{
.encryption_token = "token",
.path = "path",
},
};
auto data = static_cast<encrypt_config>(value);
EXPECT_STREQ("token", data.encryption_token.c_str());
EXPECT_STREQ("path", data.path.c_str());
}
} // namespace repertory

View File

@@ -0,0 +1,694 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "app_config.hpp"
#include "events/event_system.hpp"
#include "utils/common.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
namespace repertory {
class config_test : public ::testing::Test {
public:
console_consumer cs;
static std::atomic<std::uint64_t> idx;
std::string s3_directory;
std::string sia_directory;
void SetUp() override {
s3_directory = utils::path::combine(test::get_test_output_dir(),
{
"config_test",
"s3",
std::to_string(++idx),
});
sia_directory = utils::path::combine(test::get_test_output_dir(),
{
"config_test",
"sia",
std::to_string(++idx),
});
event_system::instance().start();
}
void TearDown() override { event_system::instance().stop(); }
};
std::atomic<std::uint64_t> config_test::idx{0U};
TEST_F(config_test, api_path) {
std::string original_value;
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_api_auth();
EXPECT_EQ(48U, original_value.size());
}
}
TEST_F(config_test, api_auth) {
std::string original_value;
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_api_auth();
config.set_api_auth(original_value.substr(0, 20));
EXPECT_EQ(original_value.substr(0, 20), config.get_api_auth());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value.substr(0, 20), config.get_api_auth());
}
}
TEST_F(config_test, api_port) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_api_port();
config.set_api_port(original_value + 5);
EXPECT_EQ(original_value + 5, config.get_api_port());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5, config.get_api_port());
}
}
TEST_F(config_test, api_user) {
std::string original_value;
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_api_user();
config.set_api_user(original_value.substr(0, 2));
EXPECT_EQ(original_value.substr(0, 2), config.get_api_user());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value.substr(0, 2), config.get_api_user());
}
}
TEST_F(config_test, download_timeout_secs) {
std::uint8_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_download_timeout_secs();
config.set_download_timeout_secs(original_value + 5);
EXPECT_EQ(original_value + 5, config.get_download_timeout_secs());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5, config.get_download_timeout_secs());
}
}
TEST_F(config_test, enable_download_timeout) {
bool original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_enable_download_timeout();
config.set_enable_download_timeout(not original_value);
EXPECT_EQ(not original_value, config.get_enable_download_timeout());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(not original_value, config.get_enable_download_timeout());
}
}
TEST_F(config_test, enable_drive_events) {
bool original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_enable_drive_events();
config.set_enable_drive_events(not original_value);
EXPECT_EQ(not original_value, config.get_enable_drive_events());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(not original_value, config.get_enable_drive_events());
}
}
#if defined(_WIN32)
TEST_F(config_test, enable_mount_manager) {
bool original_value;
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_enable_mount_manager();
config.set_enable_mount_manager(not original_value);
EXPECT_EQ(not original_value, config.get_enable_mount_manager());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(not original_value, config.get_enable_mount_manager());
}
}
#endif
TEST_F(config_test, event_level) {
{
app_config config(provider_type::sia, sia_directory);
config.set_event_level(event_level::debug);
EXPECT_EQ(event_level::debug, config.get_event_level());
config.set_event_level(event_level::warn);
EXPECT_EQ(event_level::warn, config.get_event_level());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(event_level::warn, config.get_event_level());
}
}
TEST_F(config_test, eviction_delay_mins) {
std::uint32_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_eviction_delay_mins();
config.set_eviction_delay_mins(original_value + 5);
EXPECT_EQ(original_value + 5, config.get_eviction_delay_mins());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5, config.get_eviction_delay_mins());
}
}
TEST_F(config_test, eviction_uses_accessed_time) {
bool original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_eviction_uses_accessed_time();
config.set_eviction_uses_accessed_time(not original_value);
EXPECT_EQ(not original_value, config.get_eviction_uses_accessed_time());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(not original_value, config.get_eviction_uses_accessed_time());
}
}
TEST_F(config_test, high_frequency_interval_secs) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_high_frequency_interval_secs();
config.set_high_frequency_interval_secs(original_value + 5U);
EXPECT_EQ(original_value + 5U, config.get_high_frequency_interval_secs());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5U, config.get_high_frequency_interval_secs());
}
}
TEST_F(config_test, low_frequency_interval_secs) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_low_frequency_interval_secs();
config.set_low_frequency_interval_secs(original_value + 5U);
EXPECT_EQ(original_value + 5U, config.get_low_frequency_interval_secs());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5U, config.get_low_frequency_interval_secs());
}
}
TEST_F(config_test, med_frequency_interval_secs) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_med_frequency_interval_secs();
config.set_med_frequency_interval_secs(original_value + 5U);
EXPECT_EQ(original_value + 5U, config.get_med_frequency_interval_secs());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5U, config.get_med_frequency_interval_secs());
}
}
TEST_F(config_test, max_cache_size_bytes) {
{
app_config config(provider_type::sia, sia_directory);
config.set_max_cache_size_bytes(100 * 1024 * 1024);
EXPECT_EQ(100U * 1024 * 1024, config.get_max_cache_size_bytes());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(100U * 1024 * 1024, config.get_max_cache_size_bytes());
}
}
TEST_F(config_test, max_upload_count) {
{
app_config config(provider_type::sia, sia_directory);
config.set_max_upload_count(8U);
EXPECT_EQ(std::uint8_t(8U), config.get_max_upload_count());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(std::uint8_t(8U), config.get_max_upload_count());
}
{
app_config config(provider_type::sia, sia_directory);
config.set_max_upload_count(0U);
EXPECT_EQ(std::uint8_t(1U), config.get_max_upload_count());
}
}
TEST_F(config_test, online_check_retry_secs) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_online_check_retry_secs();
config.set_online_check_retry_secs(original_value + 1);
EXPECT_EQ(original_value + 1, config.get_online_check_retry_secs());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 1, config.get_online_check_retry_secs());
}
}
TEST_F(config_test, online_check_retry_secs_minimum_value) {
{
app_config config(provider_type::sia, sia_directory);
config.set_online_check_retry_secs(14);
EXPECT_EQ(15, config.get_online_check_retry_secs());
}
}
TEST_F(config_test, orphaned_file_retention_days) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_orphaned_file_retention_days();
config.set_orphaned_file_retention_days(original_value + 1);
EXPECT_EQ(original_value + 1, config.get_orphaned_file_retention_days());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 1, config.get_orphaned_file_retention_days());
}
}
TEST_F(config_test, orphaned_file_retention_days_minimum_value) {
{
app_config config(provider_type::sia, sia_directory);
config.set_orphaned_file_retention_days(0);
EXPECT_EQ(1, config.get_orphaned_file_retention_days());
}
}
TEST_F(config_test, orphaned_file_retention_days_maximum_value) {
{
app_config config(provider_type::sia, sia_directory);
config.set_orphaned_file_retention_days(32);
EXPECT_EQ(31, config.get_orphaned_file_retention_days());
}
}
TEST_F(config_test, get_cache_directory) {
{
app_config config(provider_type::sia, sia_directory);
EXPECT_STREQ(utils::path::combine(sia_directory, {"cache"}).c_str(),
config.get_cache_directory().c_str());
}
}
TEST_F(config_test, get_config_file_path) {
{
const auto config_file = utils::path::absolute(
utils::path::combine(sia_directory, {"config.json"}));
app_config config(provider_type::sia, sia_directory);
EXPECT_STREQ(config_file.c_str(), config.get_config_file_path().c_str());
}
}
TEST_F(config_test, get_data_directory) {
{
app_config config(provider_type::sia, sia_directory);
EXPECT_STREQ(sia_directory.c_str(), config.get_data_directory().c_str());
}
}
TEST_F(config_test, get_log_directory) {
{
app_config config(provider_type::sia, sia_directory);
EXPECT_STREQ(utils::path::combine(sia_directory, {"logs"}).c_str(),
config.get_log_directory().c_str());
}
}
TEST_F(config_test, ring_buffer_file_size) {
std::uint16_t original_value;
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_ring_buffer_file_size();
config.set_ring_buffer_file_size(original_value + 5u);
EXPECT_EQ(original_value + 5u, config.get_ring_buffer_file_size());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 5u, config.get_ring_buffer_file_size());
}
}
TEST_F(config_test, ring_buffer_file_size_minimum_size) {
{
app_config config(provider_type::sia, sia_directory);
config.set_ring_buffer_file_size(63u);
EXPECT_EQ(64u, config.get_ring_buffer_file_size());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(64u, config.get_ring_buffer_file_size());
}
}
TEST_F(config_test, ring_buffer_file_size_maximum_size) {
{
app_config config(provider_type::sia, sia_directory);
config.set_ring_buffer_file_size(1025u);
EXPECT_EQ(1024u, config.get_ring_buffer_file_size());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(1024u, config.get_ring_buffer_file_size());
}
}
TEST_F(config_test, preferred_download_type) {
download_type original_value;
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_preferred_download_type();
config.set_preferred_download_type(download_type::ring_buffer);
EXPECT_NE(original_value, config.get_preferred_download_type());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_NE(original_value, config.get_preferred_download_type());
}
}
TEST_F(config_test, default_agent_name) {
EXPECT_STREQ("Sia-Agent",
app_config::default_agent_name(provider_type::sia).c_str());
}
TEST_F(config_test, default_api_port) {
EXPECT_EQ(9980U, app_config::default_api_port(provider_type::sia));
}
TEST_F(config_test, default_data_directory) {
const std::array<std::string, 1U> data_directory = {
app_config::default_data_directory(provider_type::sia),
};
#if defined(_WIN32)
const auto local_app_data = utils::get_environment_variable("localappdata");
#endif
#if defined(__linux__)
const auto local_app_data =
utils::path::combine(utils::get_environment_variable("HOME"), {".local"});
#endif
#if defined(__APPLE__)
const auto local_app_data = utils::path::combine(
utils::get_environment_variable("HOME"), {"Library/Application Support"});
#endif
auto expected_directory =
utils::path::combine(local_app_data, {"/repertory2/sia"});
EXPECT_STREQ(expected_directory.c_str(), data_directory[0].c_str());
}
TEST_F(config_test, default_rpc_port) {
EXPECT_EQ(10000U, app_config::default_rpc_port(provider_type::sia));
}
TEST_F(config_test, get_provider_display_name) {
EXPECT_STREQ(
"Sia", app_config::get_provider_display_name(provider_type::sia).c_str());
}
TEST_F(config_test, get_provider_name) {
EXPECT_STREQ("sia",
app_config::get_provider_name(provider_type::sia).c_str());
}
TEST_F(config_test, get_version) {
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(REPERTORY_CONFIG_VERSION, config.get_version());
}
}
// TEST_F(config_test, enable_remote_mount) {
// bool original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_enable_remote_mount();
// config.set_enable_remote_mount(not original_value);
// EXPECT_EQ(not original_value, config.get_enable_remote_mount());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(not original_value, config.get_enable_remote_mount());
// }
// }
// TEST_F(config_test, is_remote_mount) {
// bool original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_is_remote_mount();
// config.set_is_remote_mount(not original_value);
// EXPECT_EQ(not original_value, config.get_is_remote_mount());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(not original_value, config.get_is_remote_mount());
// }
// }
// TEST_F(config_test, enable_remote_mount_fails_if_remote_mount_is_true) {
// app_config config(provider_type::sia, sia_directory);
// config.set_is_remote_mount(true);
// config.set_enable_remote_mount(true);
// EXPECT_FALSE(config.get_enable_remote_mount());
// EXPECT_TRUE(config.get_is_remote_mount());
// }
// TEST_F(config_test, set_is_remote_mount_fails_if_enable_remote_mount_is_true)
// {
// app_config config(provider_type::sia, sia_directory);
// config.set_enable_remote_mount(true);
// config.set_is_remote_mount(true);
// EXPECT_FALSE(config.get_is_remote_mount());
// EXPECT_TRUE(config.get_enable_remote_mount());
// }
// TEST_F(config_test, remote_host_name_or_ip) {
// {
// app_config config(provider_type::sia, sia_directory);
// config.set_remote_host_name_or_ip("my.host.name");
// EXPECT_STREQ("my.host.name",
// config.get_remote_host_name_or_ip().c_str());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_STREQ("my.host.name",
// config.get_remote_host_name_or_ip().c_str());
// }
// }
// TEST_F(config_test, remote_api_port) {
// std::uint16_t original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_remote_api_port();
// config.set_remote_api_port(original_value + 5);
// EXPECT_EQ(original_value + 5, config.get_remote_api_port());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(original_value + 5, config.get_remote_api_port());
// }
// }
// TEST_F(config_test, remote_receive_timeout_secs) {
// std::uint16_t original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_remote_receive_timeout_secs();
// config.set_remote_receive_timeout_secs(original_value + 5);
// EXPECT_EQ(original_value + 5, config.get_remote_receive_timeout_secs());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(original_value + 5, config.get_remote_receive_timeout_secs());
// }
// }
// TEST_F(config_test, remote_send_timeout_secs) {
// std::uint16_t original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_remote_send_timeout_secs();
// config.set_remote_send_timeout_secs(original_value + 5);
// EXPECT_EQ(original_value + 5, config.get_remote_send_timeout_secs());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(original_value + 5, config.get_remote_send_timeout_secs());
// }
// }
// TEST_F(config_test, remote_encryption_token) {
// {
// app_config config(provider_type::sia, sia_directory);
// config.set_remote_encryption_token("myToken");
// EXPECT_STREQ("myToken", config.get_remote_encryption_token().c_str());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_STREQ("myToken", config.get_remote_encryption_token().c_str());
// }
// }
//
// TEST_F(config_test, remote_client_pool_size) {
// std::uint8_t original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_remote_client_pool_size();
// config.set_remote_client_pool_size(original_value + 5);
// EXPECT_EQ(original_value + 5, config.get_remote_client_pool_size());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(original_value + 5, config.get_remote_client_pool_size());
// }
// }
//
// TEST_F(config_test, remote_client_pool_size_minimum_value) {
// {
// app_config config(provider_type::sia, sia_directory);
// config.set_remote_client_pool_size(0);
// EXPECT_EQ(5, config.get_remote_client_pool_size());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(5, config.get_remote_client_pool_size());
// }
// }
// TEST_F(config_test, remote_max_connections) {
// std::uint8_t original_value{};
// {
// app_config config(provider_type::sia, sia_directory);
// original_value = config.get_remote_max_connections();
// config.set_remote_max_connections(original_value + 5);
// EXPECT_EQ(original_value + 5, config.get_remote_max_connections());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(original_value + 5, config.get_remote_max_connections());
// }
// }
// TEST_F(config_test, remote_max_connections_minimum_value) {
// {
// app_config config(provider_type::sia, sia_directory);
// config.set_remote_max_connections(0);
// EXPECT_EQ(1, config.get_remote_max_connections());
// }
// {
// app_config config(provider_type::sia, sia_directory);
// EXPECT_EQ(1, config.get_remote_max_connections());
// }
// }
TEST_F(config_test, retry_read_count) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_retry_read_count();
config.set_retry_read_count(original_value + 1);
EXPECT_EQ(original_value + 1, config.get_retry_read_count());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 1, config.get_retry_read_count());
}
}
TEST_F(config_test, retry_read_count_minimum_value) {
{
app_config config(provider_type::sia, sia_directory);
config.set_retry_read_count(1);
EXPECT_EQ(2, config.get_retry_read_count());
}
}
TEST_F(config_test, task_wait_ms) {
std::uint16_t original_value{};
{
app_config config(provider_type::sia, sia_directory);
original_value = config.get_task_wait_ms();
config.set_task_wait_ms(original_value + 1U);
EXPECT_EQ(original_value + 1U, config.get_task_wait_ms());
}
{
app_config config(provider_type::sia, sia_directory);
EXPECT_EQ(original_value + 1U, config.get_task_wait_ms());
}
}
TEST_F(config_test, task_wait_ms_minimum_value) {
{
app_config config(provider_type::sia, sia_directory);
config.set_task_wait_ms(1U);
EXPECT_EQ(50U, config.get_task_wait_ms());
}
}
TEST_F(config_test, can_set_database_type) {
{
app_config config(provider_type::sia, sia_directory);
config.set_database_type(database_type::rocksdb);
EXPECT_EQ(database_type::rocksdb, config.get_database_type());
config.set_database_type(database_type::sqlite);
EXPECT_EQ(database_type::sqlite, config.get_database_type());
config.set_database_type(database_type::rocksdb);
EXPECT_EQ(database_type::rocksdb, config.get_database_type());
}
}
} // namespace repertory

View File

@@ -0,0 +1,77 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "comm/curl/curl_comm.hpp"
#include "types/repertory.hpp"
namespace repertory {
TEST(curl_comm, can_create_s3_host_config) {
s3_config config{};
config.bucket = "repertory";
config.url = "https://s3.test.com";
config.region = "any";
auto hc = curl_comm::create_host_config(config, false);
EXPECT_STREQ("https", hc.protocol.c_str());
EXPECT_STREQ("repertory.s3.test.com", hc.host_name_or_ip.c_str());
EXPECT_TRUE(hc.path.empty());
}
TEST(curl_comm, can_create_s3_host_config_with_path_style) {
s3_config config{};
config.bucket = "repertory";
config.url = "https://s3.test.com";
config.region = "any";
auto hc = curl_comm::create_host_config(config, true);
EXPECT_STREQ("https", hc.protocol.c_str());
EXPECT_STREQ("s3.test.com", hc.host_name_or_ip.c_str());
EXPECT_STREQ("/repertory", hc.path.c_str());
}
TEST(curl_comm, can_create_s3_host_config_with_region) {
s3_config config{};
config.bucket = "repertory";
config.url = "https://s3.test.com";
config.region = "any";
config.use_region_in_url = true;
auto hc = curl_comm::create_host_config(config, false);
EXPECT_STREQ("https", hc.protocol.c_str());
EXPECT_STREQ("repertory.s3.any.test.com", hc.host_name_or_ip.c_str());
EXPECT_TRUE(hc.path.empty());
}
TEST(curl_comm, can_create_s3_host_config_with_region_and_path_style) {
s3_config config{};
config.bucket = "repertory";
config.url = "https://s3.test.com";
config.region = "any";
config.use_region_in_url = true;
auto hc = curl_comm::create_host_config(config, true);
EXPECT_STREQ("https", hc.protocol.c_str());
EXPECT_STREQ("s3.any.test.com", hc.host_name_or_ip.c_str());
EXPECT_STREQ("/repertory", hc.path.c_str());
}
} // namespace repertory

View File

@@ -0,0 +1,292 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "file_manager/direct_open_file.hpp"
#include "mocks/mock_provider.hpp"
namespace {
constexpr const std::size_t test_chunk_size{1024U};
} // namespace
namespace repertory {
class direct_open_file_test : public ::testing::Test {
public:
console_consumer con_consumer;
mock_provider provider;
protected:
void SetUp() override { event_system::instance().start(); }
void TearDown() override { event_system::instance().stop(); }
};
TEST_F(direct_open_file_test, read_full_file) {
auto &source_file = test::create_random_file(test_chunk_size * 32U);
auto dest_path = test::generate_test_file_name("direct_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.directory = false;
fsi.size = test_chunk_size * 32U;
std::mutex read_mtx;
EXPECT_CALL(provider, read_file_bytes)
.WillRepeatedly([&read_mtx, &source_file](
const std::string & /* api_path */, std::size_t size,
std::uint64_t offset, data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = source_file.read(data, offset, &bytes_read)
? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
direct_open_file file(test_chunk_size, 30U, fsi, provider);
auto dest_file = utils::file::file::open_or_create_file(dest_path);
EXPECT_TRUE(dest_file);
auto to_read{fsi.size};
std::size_t chunk{0U};
while (to_read > 0U) {
data_buffer data{};
EXPECT_EQ(api_error::success,
file.read(test_chunk_size, chunk * test_chunk_size, data));
std::size_t bytes_written{};
EXPECT_TRUE(
dest_file->write(data, chunk * test_chunk_size, &bytes_written));
++chunk;
to_read -= data.size();
}
dest_file->close();
source_file.close();
auto hash1 = utils::file::file(source_file.get_path()).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
TEST_F(direct_open_file_test, read_full_file_in_reverse) {
auto &source_file = test::create_random_file(test_chunk_size * 32U);
auto dest_path = test::generate_test_file_name("direct_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.directory = false;
fsi.size = test_chunk_size * 32U;
std::mutex read_mtx;
EXPECT_CALL(provider, read_file_bytes)
.WillRepeatedly([&read_mtx, &source_file](
const std::string & /* api_path */, std::size_t size,
std::uint64_t offset, data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = source_file.read(data, offset, &bytes_read)
? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
direct_open_file file(test_chunk_size, 30U, fsi, provider);
auto dest_file = utils::file::file::open_or_create_file(dest_path);
EXPECT_TRUE(dest_file);
auto to_read{fsi.size};
std::size_t chunk{file.get_total_chunks() - 1U};
while (to_read > 0U) {
data_buffer data{};
EXPECT_EQ(api_error::success,
file.read(test_chunk_size, chunk * test_chunk_size, data));
std::size_t bytes_written{};
EXPECT_TRUE(
dest_file->write(data, chunk * test_chunk_size, &bytes_written));
--chunk;
to_read -= data.size();
}
dest_file->close();
source_file.close();
auto hash1 = utils::file::file(source_file.get_path()).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
TEST_F(direct_open_file_test, read_full_file_in_partial_chunks) {
auto &source_file = test::create_random_file(test_chunk_size * 32U);
auto dest_path = test::generate_test_file_name("test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32U;
std::mutex read_mtx;
EXPECT_CALL(provider, read_file_bytes)
.WillRepeatedly([&read_mtx, &source_file](
const std::string & /* api_path */, std::size_t size,
std::uint64_t offset, data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = source_file.read(data, offset, &bytes_read)
? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
direct_open_file file(test_chunk_size, 30U, fsi, provider);
auto dest_file = utils::file::file::open_or_create_file(dest_path);
EXPECT_TRUE(dest_file);
auto total_read{std::uint64_t(0U)};
while (total_read < fsi.size) {
data_buffer data{};
EXPECT_EQ(api_error::success, file.read(3U, total_read, data));
std::size_t bytes_written{};
EXPECT_TRUE(dest_file->write(data.data(), data.size(), total_read,
&bytes_written));
total_read += data.size();
}
dest_file->close();
source_file.close();
auto hash1 = utils::file::file(source_file.get_path()).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
TEST_F(direct_open_file_test, read_full_file_in_partial_chunks_in_reverse) {
auto &source_file = test::create_random_file(test_chunk_size * 32U);
auto dest_path = test::generate_test_file_name("direct_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32U;
std::mutex read_mtx;
EXPECT_CALL(provider, read_file_bytes)
.WillRepeatedly([&read_mtx, &source_file](
const std::string & /* api_path */, std::size_t size,
std::uint64_t offset, data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = source_file.read(data, offset, &bytes_read)
? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
direct_open_file file(test_chunk_size, 30U, fsi, provider);
auto dest_file = utils::file::file::open_or_create_file(dest_path);
EXPECT_TRUE(dest_file);
std::uint64_t total_read{0U};
auto read_size{3U};
while (total_read < fsi.size) {
auto offset = fsi.size - total_read - read_size;
auto remain = fsi.size - total_read;
data_buffer data{};
EXPECT_EQ(api_error::success,
file.read(static_cast<std::size_t>(
std::min(remain, std::uint64_t(read_size))),
(remain >= read_size) ? offset : 0U, data));
std::size_t bytes_written{};
EXPECT_TRUE(dest_file->write(data, (remain >= read_size) ? offset : 0U,
&bytes_written));
total_read += data.size();
}
dest_file->close();
source_file.close();
auto hash1 = utils::file::file(source_file.get_path()).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
} // namespace repertory

View File

@@ -0,0 +1,332 @@
/*
Copyright <2018-2024> <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.
*/
#include "fixtures/file_db_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(file_db_test, file_db_types);
TYPED_TEST(file_db_test, can_add_and_remove_directory) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_directory("/", "c:\\test"));
auto list = this->file_db->get_item_list();
EXPECT_EQ(1U, list.size());
EXPECT_EQ(api_error::success, this->file_db->remove_item("/"));
list = this->file_db->get_item_list();
EXPECT_EQ(0U, list.size());
}
TYPED_TEST(file_db_test, can_add_and_remove_file) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
0U,
{},
"c:\\test\\file.txt",
}));
auto list = this->file_db->get_item_list();
EXPECT_EQ(1U, list.size());
EXPECT_EQ(api_error::success, this->file_db->remove_item("/file"));
list = this->file_db->get_item_list();
EXPECT_EQ(0U, list.size());
}
TYPED_TEST(file_db_test, can_get_api_path_for_directory) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_directory("/", "c:\\test"));
std::string api_path;
EXPECT_EQ(api_error::success,
this->file_db->get_api_path("c:\\test", api_path));
EXPECT_STREQ("/", api_path.c_str());
}
TYPED_TEST(file_db_test, can_get_api_path_for_file) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
0U,
{},
"c:\\test\\file.txt",
}));
std::string api_path;
EXPECT_EQ(api_error::success,
this->file_db->get_api_path("c:\\test\\file.txt", api_path));
EXPECT_STREQ("/file", api_path.c_str());
}
TYPED_TEST(file_db_test,
item_not_found_is_returned_for_non_existing_source_path) {
this->file_db->clear();
std::string api_path;
EXPECT_EQ(api_error::item_not_found,
this->file_db->get_api_path("c:\\test", api_path));
EXPECT_TRUE(api_path.empty());
}
TYPED_TEST(file_db_test, can_get_directory_api_path) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_directory("/", "c:\\test"));
std::string api_path;
EXPECT_EQ(api_error::success,
this->file_db->get_directory_api_path("c:\\test", api_path));
EXPECT_STREQ("/", api_path.c_str());
}
TYPED_TEST(
file_db_test,
directory_not_found_is_returned_for_non_existing_directory_source_path) {
this->file_db->clear();
std::string api_path;
EXPECT_EQ(api_error::directory_not_found,
this->file_db->get_directory_api_path("c:\\test", api_path));
EXPECT_TRUE(api_path.empty());
}
TYPED_TEST(file_db_test, can_get_file_api_path) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
0U,
{},
"c:\\test\\file.txt",
}));
std::string api_path;
EXPECT_EQ(api_error::success,
this->file_db->get_file_api_path("c:\\test\\file.txt", api_path));
EXPECT_STREQ("/file", api_path.c_str());
}
TYPED_TEST(file_db_test,
item_not_found_is_returned_for_non_existing_file_source_path) {
this->file_db->clear();
std::string api_path;
EXPECT_EQ(api_error::item_not_found,
this->file_db->get_file_api_path("c:\\test", api_path));
EXPECT_TRUE(api_path.empty());
}
TYPED_TEST(file_db_test, can_get_directory_source_path) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_directory("/", "c:\\test"));
std::string source_path;
EXPECT_EQ(api_error::success,
this->file_db->get_directory_source_path("/", source_path));
EXPECT_STREQ("c:\\test", source_path.c_str());
}
TYPED_TEST(
file_db_test,
directory_not_found_is_returned_for_non_existing_directory_api_path) {
this->file_db->clear();
std::string source_path;
EXPECT_EQ(api_error::directory_not_found,
this->file_db->get_directory_source_path("/", source_path));
EXPECT_TRUE(source_path.empty());
}
TYPED_TEST(file_db_test, can_get_file_source_path) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
0U,
{},
"c:\\test\\file.txt",
}));
std::string source_path;
EXPECT_EQ(api_error::success,
this->file_db->get_file_source_path("/file", source_path));
EXPECT_STREQ("c:\\test\\file.txt", source_path.c_str());
}
TYPED_TEST(file_db_test,
item_not_found_is_returned_for_non_existing_file_api_path) {
this->file_db->clear();
std::string source_path;
EXPECT_EQ(api_error::item_not_found,
this->file_db->get_file_source_path("/file.txt", source_path));
EXPECT_TRUE(source_path.empty());
}
TYPED_TEST(file_db_test, can_get_file_data) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
1U,
{{}, {}},
"c:\\test\\file.txt",
}));
i_file_db::file_data data{};
EXPECT_EQ(api_error::success, this->file_db->get_file_data("/file", data));
EXPECT_STREQ("/file", data.api_path.c_str());
EXPECT_EQ(1U, data.file_size);
EXPECT_EQ(2U, data.iv_list.size());
EXPECT_STREQ("c:\\test\\file.txt", data.source_path.c_str());
}
TYPED_TEST(file_db_test,
item_not_found_is_returned_for_non_existing_file_data_api_path) {
this->file_db->clear();
i_file_db::file_data data{};
EXPECT_EQ(api_error::item_not_found,
this->file_db->get_file_data("/file", data));
}
TYPED_TEST(file_db_test, can_update_existing_file_iv) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
1U,
{{}, {}},
"c:\\test\\file.txt",
}));
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
1U,
{{}, {}, {}},
"c:\\test\\file.txt",
}));
i_file_db::file_data data{};
EXPECT_EQ(api_error::success, this->file_db->get_file_data("/file", data));
EXPECT_STREQ("/file", data.api_path.c_str());
EXPECT_EQ(1U, data.file_size);
EXPECT_EQ(3U, data.iv_list.size());
EXPECT_STREQ("c:\\test\\file.txt", data.source_path.c_str());
EXPECT_EQ(1U, this->file_db->count());
}
TYPED_TEST(file_db_test, can_update_existing_file_size) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
1U,
{{}, {}},
"c:\\test\\file.txt",
}));
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
2U,
{{}, {}},
"c:\\test\\file.txt",
}));
i_file_db::file_data data{};
EXPECT_EQ(api_error::success, this->file_db->get_file_data("/file", data));
EXPECT_STREQ("/file", data.api_path.c_str());
EXPECT_EQ(2U, data.file_size);
EXPECT_EQ(2U, data.iv_list.size());
EXPECT_STREQ("c:\\test\\file.txt", data.source_path.c_str());
EXPECT_EQ(1U, this->file_db->count());
}
TYPED_TEST(file_db_test, can_update_existing_file_source_path) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
1U,
{{}, {}},
"c:\\test\\file.txt",
}));
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
1U,
{{}, {}},
"c:\\test\\file2.txt",
}));
i_file_db::file_data data{};
EXPECT_EQ(api_error::success, this->file_db->get_file_data("/file", data));
EXPECT_STREQ("/file", data.api_path.c_str());
EXPECT_EQ(1U, data.file_size);
EXPECT_EQ(2U, data.iv_list.size());
EXPECT_STREQ("c:\\test\\file2.txt", data.source_path.c_str());
EXPECT_EQ(1U, this->file_db->count());
}
TYPED_TEST(file_db_test, can_get_source_path_for_directory) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_directory("/", "c:\\test"));
std::string source_path;
EXPECT_EQ(api_error::success,
this->file_db->get_source_path("/", source_path));
EXPECT_STREQ("c:\\test", source_path.c_str());
}
TYPED_TEST(file_db_test, can_get_source_path_for_file) {
this->file_db->clear();
EXPECT_EQ(api_error::success, this->file_db->add_or_update_file({
"/file",
0U,
{},
"c:\\test\\file.txt",
}));
std::string source_path;
EXPECT_EQ(api_error::success,
this->file_db->get_source_path("/file", source_path));
EXPECT_STREQ("c:\\test\\file.txt", source_path.c_str());
}
TYPED_TEST(file_db_test, item_not_found_is_returned_for_non_existing_api_path) {
this->file_db->clear();
std::string source_path;
EXPECT_EQ(api_error::item_not_found,
this->file_db->get_source_path("/file", source_path));
EXPECT_TRUE(source_path.empty());
}
} // namespace repertory

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
/*
Copyright <2018-2024> <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.
*/
#include "fixtures/file_mgr_db_fixture.hpp"
#include <utils/time.hpp>
namespace repertory {
TYPED_TEST_CASE(file_mgr_db_test, file_mgr_db_types);
TYPED_TEST(file_mgr_db_test, can_add_and_remove_resume) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
2ULL,
{},
"/src/test0",
}));
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(1U, list.size());
EXPECT_EQ(1U, list.size());
EXPECT_STREQ("/test0", list.at(0U).api_path.c_str());
EXPECT_EQ(2ULL, list.at(0U).chunk_size);
EXPECT_STREQ("/src/test0", list.at(0U).source_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_resume("/test0"));
list = this->file_mgr_db->get_resume_list();
EXPECT_TRUE(list.empty());
}
TYPED_TEST(file_mgr_db_test, can_get_resume_list) {
this->file_mgr_db->clear();
for (auto idx = 0U; idx < 5U; ++idx) {
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test1_" + std::to_string(idx),
2ULL + idx,
{},
"/src/test1_" + std::to_string(idx),
}));
}
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(5U, list.size());
for (auto idx = 0U; idx < list.size(); ++idx) {
EXPECT_STREQ(("/test1_" + std::to_string(idx)).c_str(),
list.at(idx).api_path.c_str());
EXPECT_EQ(2ULL + idx, list.at(idx).chunk_size);
EXPECT_STREQ(("/src/test1_" + std::to_string(idx)).c_str(),
list.at(idx).source_path.c_str());
}
}
TYPED_TEST(file_mgr_db_test, can_replace_resume) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
2ULL,
{},
"/src/test0",
}));
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
3ULL,
{},
"/src/test1",
}));
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(1U, list.size());
EXPECT_STREQ("/test0", list.at(0U).api_path.c_str());
EXPECT_EQ(3ULL, list.at(0U).chunk_size);
EXPECT_STREQ("/src/test1", list.at(0U).source_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_resume("/test0"));
}
TYPED_TEST(file_mgr_db_test, can_rename_resume) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_resume({
"/test0",
2ULL,
{},
"/src/test0",
}));
EXPECT_TRUE(this->file_mgr_db->rename_resume("/test0", "/test1"));
auto list = this->file_mgr_db->get_resume_list();
EXPECT_EQ(1U, list.size());
EXPECT_STREQ("/test1", list.at(0U).api_path.c_str());
EXPECT_EQ(2ULL, list.at(0U).chunk_size);
EXPECT_STREQ("/src/test0", list.at(0U).source_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_resume("/test1"));
}
TYPED_TEST(file_mgr_db_test, can_add_get_and_remove_upload) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_upload({
"/test0",
"/src/test0",
}));
auto upload = this->file_mgr_db->get_upload("/test0");
EXPECT_TRUE(upload.has_value());
EXPECT_TRUE(this->file_mgr_db->remove_upload("/test0"));
upload = this->file_mgr_db->get_next_upload();
EXPECT_FALSE(upload.has_value());
}
TYPED_TEST(file_mgr_db_test, uploads_are_correctly_ordered) {
this->file_mgr_db->clear();
EXPECT_TRUE(this->file_mgr_db->add_upload({
"/test08",
"/src/test0",
}));
EXPECT_TRUE(this->file_mgr_db->add_upload({
"/test07",
"/src/test1",
}));
auto upload = this->file_mgr_db->get_next_upload();
EXPECT_TRUE(upload.has_value());
EXPECT_STREQ("/test08", upload->api_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_upload("/test08"));
upload = this->file_mgr_db->get_next_upload();
EXPECT_TRUE(upload.has_value());
EXPECT_STREQ("/test07", upload->api_path.c_str());
EXPECT_TRUE(this->file_mgr_db->remove_upload("/test07"));
upload = this->file_mgr_db->get_next_upload();
EXPECT_FALSE(upload.has_value());
}
} // namespace repertory

View File

@@ -0,0 +1,125 @@
/*
Copyright <2018-2024> <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/fuse_fixture.hpp"
namespace {
const auto access_permutations = {
// clang-format off
std::make_tuple(0000, R_OK, -1, EACCES), // No permissions, R_OK
std::make_tuple(0000, W_OK, -1, EACCES), // No permissions, W_OK
std::make_tuple(0000, X_OK, -1, EACCES), // No permissions, X_OK
std::make_tuple(0444, R_OK, 0, 0), // Read-only, R_OK
std::make_tuple(0444, W_OK, -1, EACCES), // Read-only, W_OK
std::make_tuple(0444, X_OK, -1, EACCES), // Read-only, X_OK
std::make_tuple(0222, R_OK, -1, EACCES), // Write-only, R_OK
std::make_tuple(0222, W_OK, 0, 0), // Write-only, W_OK
std::make_tuple(0222, X_OK, -1, EACCES), // Write-only, X_OK
std::make_tuple(0111, R_OK, -1, EACCES), // Execute-only, R_OK
std::make_tuple(0111, W_OK, -1, EACCES), // Execute-only, W_OK
std::make_tuple(0111, X_OK, 0, 0), // Execute-only, X_OK
std::make_tuple(0666, R_OK | W_OK, 0, 0), // Read and write, R_OK | W_OK
std::make_tuple(0666, R_OK | X_OK, -1, EACCES), // Read and write, R_OK | X_OK
std::make_tuple(0777, R_OK | W_OK | X_OK, 0, 0), // All permissions
std::make_tuple(0555, R_OK | X_OK, 0, 0), // Read and execute, R_OK | X_OK
std::make_tuple(0555, W_OK, -1, EACCES) // Read and execute, W_OK
// clang-format on
};
void perform_access_test(auto &&permutation, auto &&item_path) {
mode_t permissions{};
int access_mode{};
int expected_result{};
int expected_errno{};
std::tie(permissions, access_mode, expected_result, expected_errno) =
permutation;
EXPECT_EQ(0, chmod(item_path.c_str(), permissions));
auto result = access(item_path.c_str(), access_mode);
EXPECT_EQ(result, expected_result)
<< "Expected access result to be " << expected_result
<< " for permissions " << std::oct << permissions << " and access mode "
<< access_mode;
if (result == -1) {
EXPECT_EQ(errno, expected_errno)
<< "Expected errno to be " << expected_errno << " for permissions "
<< std::oct << permissions;
}
}
} // namespace
namespace repertory {
TYPED_TEST_CASE(fuse_test, fuse_provider_types);
TYPED_TEST(fuse_test, access_can_check_if_item_does_not_exist) {
EXPECT_EQ(
-1,
access(utils::path::combine(this->mount_location, {"test_dir"}).c_str(),
F_OK));
EXPECT_EQ(ENOENT, utils::get_last_error_code());
}
TYPED_TEST(fuse_test, access_can_check_if_directory_exists) {
std::string dir_name{"access_test"};
auto dir_path = this->create_directory_and_test(dir_name);
EXPECT_EQ(0, access(dir_path.c_str(), F_OK));
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, access_can_check_if_file_exists) {
std::string file_name{"access_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(0, access(file_path.c_str(), F_OK));
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, access_directory_permutations_test) {
std::string dir_name{"access_test"};
auto dir_path = this->create_directory_and_test(dir_name);
for (auto &&permutation : access_permutations) {
perform_access_test(permutation, dir_path);
}
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, access_file_permutations_test) {
std::string file_name{"access_test"};
auto file_path = this->create_file_and_test(file_name);
for (auto &&permutation : access_permutations) {
perform_access_test(permutation, file_path);
}
this->unlink_file_and_test(file_path);
}
} // namespace repertory
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,85 @@
/*
Copyright <2018-2024> <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/fuse_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(fuse_test, fuse_provider_types);
TYPED_TEST(fuse_test, chmod_can_not_chmod_set_sticky_if_not_root) {
std::string file_name{"chmod_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR | S_ISVTX));
EXPECT_EQ(EPERM, errno);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, chmod_can_chmod_if_owner) {
std::string file_name{"chmod_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(0, chmod(file_path.c_str(), S_IRUSR | S_IWUSR));
std::this_thread::sleep_for(SLEEP_SECONDS);
struct stat64 unix_st {};
stat64(file_path.c_str(), &unix_st);
EXPECT_EQ(static_cast<std::uint32_t>(S_IRUSR | S_IWUSR),
ACCESSPERMS & unix_st.st_mode);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, chmod_can_not_chmod_if_not_owner) {
std::string file_name{"chmod_test"};
auto file_path = this->create_root_file(file_name);
EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR));
EXPECT_EQ(EPERM, errno);
this->unlink_root_file(file_path);
}
TYPED_TEST(fuse_test, chmod_can_not_chmod_setgid_if_not_root) {
std::string file_name{"chmod_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR | S_ISGID));
EXPECT_EQ(EPERM, errno);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, chmod_can_not_chmod_setuid_if_not_root) {
std::string file_name{"chmod_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(-1, chmod(file_path.c_str(), S_IRUSR | S_IWUSR | S_ISUID));
EXPECT_EQ(EPERM, errno);
this->unlink_file_and_test(file_path);
}
} // namespace repertory
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,124 @@
/*
Copyright <2018-2024> <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/fuse_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(fuse_test, fuse_provider_types);
TYPED_TEST(fuse_test,
chown_can_chown_group_if_owner_and_a_member_of_the_group) {
std::string file_name{"chown_test"};
auto file_path = this->create_file_and_test(file_name);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(0, chown(file_path.c_str(), static_cast<uid_t>(-1), getgid()));
std::this_thread::sleep_for(SLEEP_SECONDS);
struct stat64 unix_st2 {};
stat64(file_path.c_str(), &unix_st2);
EXPECT_EQ(getgid(), unix_st2.st_gid);
EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(
fuse_test,
chown_can_chown_group_when_specifying_owner_and_a_member_of_the_group) {
std::string file_name{"chown_test"};
auto file_path = this->create_file_and_test(file_name);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(0, chown(file_path.c_str(), getuid(), getgid()));
std::this_thread::sleep_for(SLEEP_SECONDS);
struct stat64 unix_st2 {};
stat64(file_path.c_str(), &unix_st2);
EXPECT_EQ(getgid(), unix_st2.st_gid);
EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test,
chown_can_not_chown_group_if_owner_but_not_a_member_of_the_group) {
std::string file_name{"chown_test"};
auto file_path = this->create_file_and_test(file_name);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(-1, chown(file_path.c_str(), static_cast<uid_t>(-1), 0));
EXPECT_EQ(EPERM, errno);
struct stat64 unix_st2 {};
stat64(file_path.c_str(), &unix_st2);
EXPECT_EQ(unix_st.st_gid, unix_st2.st_gid);
EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, chown_can_not_chown_group_if_not_the_owner) {
std::string file_name{"chown_test"};
auto file_path = this->create_root_file(file_name);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(-1, chown(file_path.c_str(), static_cast<uid_t>(-1), getgid()));
EXPECT_EQ(EPERM, errno);
struct stat64 unix_st2 {};
stat64(file_path.c_str(), &unix_st2);
EXPECT_EQ(unix_st.st_gid, unix_st2.st_gid);
EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid);
this->unlink_root_file(file_path);
}
TYPED_TEST(fuse_test, chown_can_not_chown_user_if_not_root) {
std::string file_name{"chown_test"};
auto file_path = this->create_file_and_test(file_name);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(-1, chown(file_path.c_str(), 0, static_cast<gid_t>(-1)));
EXPECT_EQ(EPERM, errno);
struct stat64 unix_st2 {};
stat64(file_path.c_str(), &unix_st2);
EXPECT_EQ(unix_st.st_gid, unix_st2.st_gid);
EXPECT_EQ(unix_st.st_uid, unix_st2.st_uid);
this->unlink_file_and_test(file_path);
}
} // namespace repertory
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,521 @@
/*
Copyright <2018-2024> <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/fuse_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(fuse_test, fuse_provider_types);
TYPED_TEST(fuse_test, create_can_create_and_remove_directory) {
std::string dir_name{"create_test"};
auto dir_path = this->create_directory_and_test(dir_name);
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, create_can_create_and_remove_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, create_can_create_directory_with_specific_perms) {
std::string dir_name{"create_test"};
auto dir_path = this->create_directory_and_test(dir_name, S_IRUSR);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(dir_path.c_str(), &unix_st));
EXPECT_EQ(S_IRUSR, unix_st.st_mode & ACCESSPERMS);
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, create_can_create_file_with_specific_perms) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name, S_IRUSR);
struct stat64 unix_st {};
EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
EXPECT_EQ(S_IRUSR, unix_st.st_mode & ACCESSPERMS);
this->unlink_file_and_test(file_path);
}
// 1. Create File - O_CREAT
TYPED_TEST(fuse_test, create_can_create_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 2. Create File - O_CREAT | O_WRONLY
TYPED_TEST(fuse_test, create_can_create_file_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 3. Create File - O_CREAT | O_RDWR
TYPED_TEST(fuse_test, create_can_create_file_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT | O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 4. Create File - O_CREAT | O_TRUNC
TYPED_TEST(fuse_test, create_can_create_with_truncate_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT | O_TRUNC, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
auto size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(0U, *size);
this->unlink_file_and_test(file_path);
}
// 5. Create File - O_CREAT | O_TRUNC | O_WRONLY
TYPED_TEST(fuse_test, create_can_create_with_truncate_file_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle =
open(file_path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
auto size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(0U, *size);
this->unlink_file_and_test(file_path);
}
// 6. Create File - O_CREAT | O_TRUNC | O_RDWR
TYPED_TEST(fuse_test, create_can_create_with_truncate_file_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle =
open(file_path.c_str(), O_CREAT | O_TRUNC | O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
auto size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(0U, *size);
this->unlink_file_and_test(file_path);
}
// 7. Create File - O_CREAT | O_APPEND
TYPED_TEST(fuse_test, create_can_create_file_for_append) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT | O_APPEND, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 8. Create File - O_CREAT | O_APPEND | O_WRONLY
TYPED_TEST(fuse_test, create_can_create_file_for_append_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle =
open(file_path.c_str(), O_CREAT | O_APPEND | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 9. Create File - O_CREAT | O_EXCL | O_WRONLY (file does not exist)
TYPED_TEST(fuse_test, create_can_create_file_excl_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle =
open(file_path.c_str(), O_CREAT | O_EXCL | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 10. Create File - O_CREAT | O_EXCL | O_RDWR (file does not exist)
TYPED_TEST(fuse_test, create_can_create_file_excl_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT | O_EXCL | O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 11. Create File - O_CREAT | O_EXCL (file does not exist)
TYPED_TEST(fuse_test, create_can_create_file_excl) {
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
auto handle = open(file_path.c_str(), O_CREAT | O_EXCL, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 1. Open Existing File - O_RDONLY
TYPED_TEST(fuse_test, create_can_open_existing_file_ro) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_RDONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 2. Open Existing File - O_WRONLY
TYPED_TEST(fuse_test, create_can_open_existing_file_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 3. Open Existing File - O_RDWR
TYPED_TEST(fuse_test, create_can_open_existing_file_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 4. Open Existing File - O_APPEND
TYPED_TEST(fuse_test, create_can_open_existing_file_for_append) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_APPEND, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 5. Open Existing File - O_APPEND | O_WRONLY
TYPED_TEST(fuse_test, create_can_open_existing_file_for_append_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_APPEND | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 6. Open Existing File - O_APPEND | O_RDWR
TYPED_TEST(fuse_test, create_can_open_existing_file_for_append_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_APPEND | O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 7. Open Existing File - O_TRUNC | O_WRONLY
TYPED_TEST(fuse_test, create_can_open_and_truncate_existing_file_wo) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(0, truncate(file_path.c_str(), 24U));
auto size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(24U, *size);
auto handle = open(file_path.c_str(), O_TRUNC | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(0U, *size);
this->unlink_file_and_test(file_path);
}
// 8. Open Existing File - O_TRUNC | O_RDWR
TYPED_TEST(fuse_test, create_can_open_and_truncate_existing_file_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(0, truncate(file_path.c_str(), 24U));
auto size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(24U, *size);
auto handle = open(file_path.c_str(), O_TRUNC | O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(0U, *size);
this->unlink_file_and_test(file_path);
}
// 9. Open Existing File - O_TRUNC
TYPED_TEST(fuse_test, create_can_open_and_truncate_existing_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
EXPECT_EQ(0, truncate(file_path.c_str(), 24U));
auto size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(24U, *size);
auto handle = open(file_path.c_str(), O_TRUNC, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
size = utils::file::file{file_path}.size();
EXPECT_TRUE(size.has_value());
EXPECT_EQ(0U, *size);
this->unlink_file_and_test(file_path);
}
// 10. Open Existing File - O_EXCL | O_WRONLY (file does not exist)
TYPED_TEST(fuse_test, create_can_open_existing_file_with_excl_wr) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_EXCL | O_WRONLY, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
// 11. Open Existing File - O_EXCL | O_RDWR (file does not exist)
TYPED_TEST(fuse_test, create_can_open_existing_file_with_excl_rw) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_EXCL | O_RDWR, ACCESSPERMS);
EXPECT_LE(1, handle);
close(handle);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, create_fails_with_excl_if_path_is_directory) {
std::array<int, 3U> ops{
O_CREAT | O_EXCL,
O_CREAT | O_EXCL | O_RDWR,
O_CREAT | O_EXCL | O_WRONLY,
};
std::string dir_name{"create_test"};
auto dir_path = this->create_directory_and_test(dir_name);
for (auto &&flags : ops) {
auto handle = open(dir_path.c_str(), flags, ACCESSPERMS);
EXPECT_EQ(-1, handle);
EXPECT_EQ(EEXIST, errno);
}
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, create_fails_with_excl_if_file_exists) {
std::array<int, 3U> ops{
O_CREAT | O_EXCL,
O_CREAT | O_EXCL | O_RDWR,
O_CREAT | O_EXCL | O_WRONLY,
};
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
for (auto &&flags : ops) {
auto handle = open(file_path.c_str(), flags, ACCESSPERMS);
EXPECT_EQ(-1, handle);
EXPECT_EQ(EEXIST, errno);
}
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, create_fails_if_path_is_directory) {
std::array<int, 7U> ops{
O_CREAT | O_APPEND,
O_CREAT | O_RDWR,
O_CREAT | O_TRUNC | O_RDWR,
O_CREAT | O_TRUNC | O_WRONLY,
O_CREAT | O_TRUNC,
O_CREAT | O_WRONLY,
O_CREAT,
};
std::string dir_name{"create_test"};
auto dir_path = this->create_directory_and_test(dir_name);
for (auto &&flags : ops) {
auto handle = open(dir_path.c_str(), flags, ACCESSPERMS);
EXPECT_EQ(-1, handle);
EXPECT_EQ(EISDIR, errno);
}
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, create_fails_if_parent_path_does_not_exist) {
std::array<int, 10U> ops{
O_CREAT | O_APPEND,
O_CREAT | O_EXCL,
O_CREAT | O_EXCL | O_RDWR,
O_CREAT | O_EXCL | O_WRONLY,
O_CREAT | O_RDWR,
O_CREAT | O_TRUNC | O_RDWR,
O_CREAT | O_TRUNC | O_WRONLY,
O_CREAT | O_TRUNC,
O_CREAT | O_WRONLY,
O_CREAT,
};
std::string file_name{"no_dir/create_test"};
auto file_path = this->create_file_path(file_name);
for (auto &&flags : ops) {
auto handle = open(file_path.c_str(), flags, ACCESSPERMS);
EXPECT_EQ(-1, handle);
EXPECT_EQ(ENOENT, errno);
}
}
TYPED_TEST(fuse_test, create_fails_if_invalid) {
std::array<int, 1U> ops{
O_CREAT | O_TRUNC | O_APPEND,
};
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
for (auto &&flags : ops) {
auto handle = open(file_path.c_str(), flags, ACCESSPERMS);
EXPECT_EQ(-1, handle);
EXPECT_EQ(EINVAL, errno);
}
}
TYPED_TEST(fuse_test, create_open_fails_if_path_is_directory) {
std::array<int, 9U> ops{
O_APPEND, O_EXCL | O_WRONLY, O_RDWR | O_APPEND,
O_RDWR | O_EXCL, O_RDWR | O_TRUNC, O_RDWR,
O_TRUNC, O_WRONLY | O_APPEND, O_WRONLY,
};
std::string dir_name{"create_test"};
auto dir_path = this->create_directory_and_test(dir_name);
for (auto &&flags : ops) {
auto handle = open(dir_path.c_str(), flags);
EXPECT_EQ(-1, handle);
if (handle != -1) {
std::cout << std::oct << flags << std::endl;
}
EXPECT_EQ(EISDIR, errno);
}
this->rmdir_and_test(dir_path);
}
TYPED_TEST(fuse_test, create_open_fails_if_path_does_not_exist) {
std::array<int, 10U> ops{
O_APPEND,
O_EXCL | O_WRONLY,
O_EXCL,
O_RDWR | O_APPEND,
O_RDWR | O_EXCL,
O_RDWR | O_TRUNC,
O_RDWR,
O_TRUNC,
O_WRONLY | O_APPEND,
O_WRONLY,
};
std::string file_name{"create_test"};
auto file_path = this->create_file_path(file_name);
for (auto &&flags : ops) {
auto handle = open(file_path.c_str(), flags);
EXPECT_EQ(-1, handle);
EXPECT_EQ(ENOENT, errno);
}
}
} // namespace repertory
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,154 @@
/*
Copyright <2018-2024> <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/fuse_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(fuse_test, fuse_provider_types);
TYPED_TEST(fuse_test, rdrw_can_read_and_write_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_RDWR);
ASSERT_GT(handle, -1);
auto write_buffer = utils::generate_secure_random<data_buffer>(8096U);
auto bytes_written =
pwrite64(handle, write_buffer.data(), write_buffer.size(), 0U);
EXPECT_EQ(write_buffer.size(), bytes_written);
data_buffer read_buffer(write_buffer.size());
auto bytes_read = pread64(handle, read_buffer.data(), read_buffer.size(), 0U);
EXPECT_EQ(bytes_written, bytes_read);
EXPECT_EQ(0, std::memcmp(write_buffer.data(), read_buffer.data(),
write_buffer.size()));
close(handle);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, rdrw_can_read_from_offset) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_RDWR);
ASSERT_GT(handle, -1);
auto write_buffer = utils::generate_secure_random<data_buffer>(8096U);
auto bytes_written =
pwrite64(handle, write_buffer.data(), write_buffer.size(), 0U);
EXPECT_EQ(write_buffer.size(), bytes_written);
data_buffer read_buffer(1U);
for (std::size_t idx = 0U; idx < write_buffer.size(); ++idx) {
auto bytes_read =
pread64(handle, read_buffer.data(), read_buffer.size(), idx);
EXPECT_EQ(1U, bytes_read);
EXPECT_EQ(write_buffer.at(idx), read_buffer.at(0U));
}
close(handle);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, rdrw_can_read_from_offset_after_eof) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_RDWR);
ASSERT_GT(handle, -1);
auto write_buffer = utils::generate_secure_random<data_buffer>(8096U);
auto bytes_written =
pwrite64(handle, write_buffer.data(), write_buffer.size(), 0U);
EXPECT_EQ(write_buffer.size(), bytes_written);
data_buffer read_buffer(1U);
for (std::size_t idx = 0U; idx < write_buffer.size() + 1U; ++idx) {
auto bytes_read =
pread64(handle, read_buffer.data(), read_buffer.size(), idx);
if (idx == write_buffer.size()) {
EXPECT_EQ(0U, bytes_read);
} else {
EXPECT_EQ(1U, bytes_read);
EXPECT_EQ(write_buffer.at(idx), read_buffer.at(0U));
}
}
close(handle);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, rdrw_can_not_write_to_ro_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_RDONLY);
ASSERT_GT(handle, -1);
auto write_buffer = utils::generate_secure_random<data_buffer>(8096U);
auto bytes_written =
pwrite64(handle, write_buffer.data(), write_buffer.size(), 0U);
EXPECT_EQ(-1, bytes_written);
EXPECT_EQ(EBADF, errno);
auto file_size = utils::file::file{file_path}.size();
EXPECT_TRUE(file_size.has_value());
EXPECT_EQ(0U, *file_size);
close(handle);
this->unlink_file_and_test(file_path);
}
TYPED_TEST(fuse_test, rdrw_can_not_read_from_wo_file) {
std::string file_name{"create_test"};
auto file_path = this->create_file_and_test(file_name);
auto handle = open(file_path.c_str(), O_WRONLY);
ASSERT_GT(handle, -1);
auto write_buffer = utils::generate_secure_random<data_buffer>(8096U);
auto bytes_written =
pwrite64(handle, write_buffer.data(), write_buffer.size(), 0U);
EXPECT_EQ(write_buffer.size(), bytes_written);
data_buffer read_buffer(1U);
auto bytes_read =
pread64(handle, read_buffer.data(), read_buffer.size(), idx);
EXPECT_EQ(-1, bytes_read);
EXPECT_EQ(EBADF, errno);
close(handle);
this->unlink_file_and_test(file_path);
}
} // namespace repertory
#endif // !defined(_WIN32)

View File

@@ -0,0 +1,411 @@
// static void test_rename_file(const std::string &from_file_path,
// const std::string &to_file_path,
// bool is_rename_supported) {
// std::cout << __FUNCTION__ << std::endl;
// auto fd = open(from_file_path.c_str(), O_RDWR, S_IRUSR | S_IWUSR |
// S_IRGRP); EXPECT_LE(1, fd); close(fd);
//
// std::this_thread::sleep_for(SLEEP_SECONDS);
//
// if (is_rename_supported) {
// EXPECT_EQ(0, rename(from_file_path.c_str(), to_file_path.c_str()));
// EXPECT_FALSE(utils::file::is_file(from_file_path));
// EXPECT_TRUE(utils::file::is_file(to_file_path));
// } else {
// EXPECT_EQ(-1, rename(from_file_path.c_str(), to_file_path.c_str()));
// EXPECT_TRUE(utils::file::is_file(from_file_path));
// EXPECT_FALSE(utils::file::is_file(to_file_path));
// }
// }
//
// static void test_rename_directory(const std::string &from_dir_path,
// const std::string &to_dir_path,
// bool is_rename_supported) {
// std::cout << __FUNCTION__ << std::endl;
// EXPECT_EQ(0, mkdir(from_dir_path.c_str(),
// S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP));
// std::this_thread::sleep_for(SLEEP_SECONDS);
//
// EXPECT_TRUE(utils::file::is_directory(from_dir_path));
// if (is_rename_supported) {
// EXPECT_EQ(0, rename(from_dir_path.c_str(), to_dir_path.c_str()));
// EXPECT_FALSE(utils::file::is_directory(from_dir_path));
// EXPECT_TRUE(utils::file::is_directory(to_dir_path));
// } else {
// EXPECT_EQ(-1, rename(from_dir_path.c_str(), to_dir_path.c_str()));
// EXPECT_TRUE(utils::file::is_directory(from_dir_path));
// EXPECT_FALSE(utils::file::is_directory(to_dir_path));
// }
// }
//
// 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) {
// std::cout << __FUNCTION__ << std::endl;
// auto file =
// open(file_path.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP);
// EXPECT_LE(1, file);
// EXPECT_EQ(0, fallocate(file, 0, 0, 16));
//
// std::uint64_t file_size{};
// EXPECT_TRUE(utils::file::get_file_size(file_path, file_size));
// EXPECT_EQ(16U, file_size);
//
// EXPECT_EQ(0, close(file));
//
// file_size = 0U;
// EXPECT_TRUE(utils::file::get_file_size(file_path, file_size));
// EXPECT_EQ(16U, file_size);
// }
// #endif
//
// static void test_file_getattr(const std::string & /* api_path */,
// const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// auto fd =
// open(file_path.c_str(), O_CREAT | O_RDONLY, S_IRUSR | S_IWUSR |
// S_IRGRP);
// EXPECT_LE(1, fd);
//
// EXPECT_EQ(0, close(fd));
//
// struct stat64 unix_st {};
// EXPECT_EQ(0, stat64(file_path.c_str(), &unix_st));
// EXPECT_EQ(static_cast<std::uint32_t>(S_IRUSR | S_IWUSR | S_IRGRP),
// ACCESSPERMS & unix_st.st_mode);
// EXPECT_FALSE(S_ISDIR(unix_st.st_mode));
// EXPECT_TRUE(S_ISREG(unix_st.st_mode));
// }
//
// static void test_directory_getattr(const std::string & /* api_path */,
// const std::string &directory_path) {
// std::cout << __FUNCTION__ << std::endl;
// EXPECT_EQ(0, mkdir(directory_path.c_str(),
// S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP));
//
// struct stat64 unix_st {};
// EXPECT_EQ(0, stat64(directory_path.c_str(), &unix_st));
// EXPECT_EQ(static_cast<std::uint32_t>(S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP
// |
// S_IXGRP),
// ACCESSPERMS & unix_st.st_mode);
// EXPECT_TRUE(S_ISDIR(unix_st.st_mode));
// EXPECT_FALSE(S_ISREG(unix_st.st_mode));
// }
//
// static void
// test_write_operations_fail_if_read_only(const std::string & /* api_path */,
// const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// auto fd =
// open(file_path.c_str(), O_CREAT | O_RDONLY, S_IRUSR | S_IWUSR |
// S_IRGRP);
// EXPECT_LE(1, fd);
//
// std::string data = "TestData";
// EXPECT_EQ(-1, write(fd, data.data(), data.size()));
//
// EXPECT_EQ(-1, ftruncate(fd, 9u));
//
// #if !defined(__APPLE__)
// EXPECT_EQ(-1, fallocate(fd, 0, 0, 16));
// #endif
//
// EXPECT_EQ(0, close(fd));
//
// std::this_thread::sleep_for(SLEEP_SECONDS);
//
// std::uint64_t file_size{};
// EXPECT_TRUE(utils::file::get_file_size(file_path, file_size));
// EXPECT_EQ(std::size_t(0u), file_size);
//
// // filesystem_item fsi{};
// // EXPECT_EQ(api_error::success,
// // provider.get_filesystem_item(api_path, false, fsi));
// // EXPECT_TRUE(utils::file::get_file_size(fsi.source_path, file_size));
// // EXPECT_EQ(std::size_t(0u), file_size);
// }
//
// #if !__APPLE__ && HAS_SETXATTR
// static void test_xattr_invalid_parameters(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(-1, setxattr(nullptr, "user.test_attr", attr.data(), attr.size(),
// XATTR_CREATE));
// EXPECT_EQ(errno, EFAULT);
// EXPECT_EQ(-1, setxattr(file_path.c_str(), nullptr, attr.data(),
// attr.size(),
// XATTR_CREATE));
// EXPECT_EQ(errno, EFAULT);
// EXPECT_EQ(-1, setxattr(file_path.c_str(), "user.test_attr", nullptr,
// attr.size(), XATTR_CREATE));
// EXPECT_EQ(errno, EFAULT);
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", nullptr, 0,
// XATTR_CREATE));
// }
//
// static void test_xattr_create_and_get(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_CREATE));
//
// std::string val;
// val.resize(attr.size());
// EXPECT_EQ(attr.size(),
// static_cast<std::size_t>(getxattr(
// file_path.c_str(), "user.test_attr", val.data(),
// val.size())));
// EXPECT_STREQ(attr.c_str(), val.c_str());
// }
//
// static void test_xattr_listxattr(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_CREATE));
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr2", attr.data(),
// attr.size(), XATTR_CREATE));
//
// std::string val;
// val.resize(attr.size());
// EXPECT_EQ(attr.size(),
// static_cast<std::size_t>(getxattr(
// file_path.c_str(), "user.test_attr", val.data(),
// val.size())));
// EXPECT_STREQ(attr.c_str(), val.c_str());
//
// std::string data;
// auto size = listxattr(file_path.c_str(), data.data(), 0U);
// EXPECT_EQ(31, size);
//
// data.resize(static_cast<std::size_t>(size));
// EXPECT_EQ(size, listxattr(file_path.c_str(), data.data(),
// static_cast<std::size_t>(size)));
//
// auto *ptr = data.data();
// EXPECT_STREQ("user.test_attr", ptr);
//
// ptr += strlen(ptr) + 1;
// EXPECT_STREQ("user.test_attr2", ptr);
// }
//
// static void test_xattr_replace(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_CREATE));
//
// attr = "cow";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_REPLACE));
//
// std::string val;
// val.resize(attr.size());
// EXPECT_EQ(attr.size(),
// static_cast<std::size_t>(getxattr(
// file_path.c_str(), "user.test_attr", val.data(),
// val.size())));
// EXPECT_STREQ(attr.c_str(), val.c_str());
// }
//
// static void test_xattr_default_create(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), 0));
//
// std::string val;
// val.resize(attr.size());
// EXPECT_EQ(attr.size(),
// static_cast<std::size_t>(getxattr(
// file_path.c_str(), "user.test_attr", val.data(),
// val.size())));
// EXPECT_STREQ(attr.c_str(), val.c_str());
// }
//
// static void test_xattr_default_replace(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), 0));
//
// attr = "cow";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), 0));
//
// std::string val;
// val.resize(attr.size());
// EXPECT_EQ(attr.size(),
// static_cast<std::size_t>(getxattr(
// file_path.c_str(), "user.test_attr", val.data(),
// val.size())));
// EXPECT_STREQ(attr.c_str(), val.c_str());
// }
//
// static void test_xattr_create_fails_if_exists(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), 0));
// EXPECT_EQ(-1, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_CREATE));
// EXPECT_EQ(EEXIST, errno);
// }
//
// static void
// test_xattr_create_fails_if_not_exists(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(-1, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_REPLACE));
// EXPECT_EQ(ENODATA, errno);
// }
//
// static void test_xattr_removexattr(const std::string &file_path) {
// std::cout << __FUNCTION__ << std::endl;
// std::string attr = "moose";
// EXPECT_EQ(0, setxattr(file_path.c_str(), "user.test_attr", attr.data(),
// attr.size(), XATTR_CREATE));
//
// EXPECT_EQ(0, removexattr(file_path.c_str(), "user.test_attr"));
//
// std::string val;
// val.resize(attr.size());
// EXPECT_EQ(-1, getxattr(file_path.c_str(), "user.test_attr", val.data(),
// val.size()));
// EXPECT_EQ(ENODATA, errno);
// }
// #endif
//
// // file_path = create_file_and_test(mount_location,
// // "write_read_test");
// // test_write_and_read(utils::path::create_api_path("write_read_test"),
// // file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path =
// // create_file_and_test(mount_location, "from_rename_file_test");
// // auto to_file_path =
// // utils::path::combine(mount_location, {"to_rename_file_test"});
// // test_rename_file(file_path, to_file_path,
// // provider_ptr->is_rename_supported());
// // EXPECT_TRUE(utils::file::file(file_path).remove());
// // EXPECT_TRUE(utils::file::file(to_file_path).remove());
// //
// // file_path =
// // utils::path::combine(mount_location,
// {"from_rename_dir_test"});
// // to_file_path =
// // utils::path::combine(mount_location, {"to_rename_dir_test"});
// // test_rename_directory(file_path, to_file_path,
// // provider_ptr->is_rename_supported());
// // EXPECT_TRUE(utils::file::retry_delete_directory(file_path.c_str()));
// // EXPECT_TRUE(utils::file::retry_delete_directory(to_file_path.c_str()));
// //
// // file_path = create_file_and_test(mount_location,
// // "truncate_file_test"); test_truncate(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path = create_file_and_test(mount_location,
// // "ftruncate_file_test"); test_ftruncate(file_path);
// // unlink_file_and_test(file_path);
// //
// // #if !defined(__APPLE__)
// // file_path = create_file_and_test(mount_location,
// // "fallocate_file_test");
// // test_fallocate(utils::path::create_api_path("fallocate_file_test"),
// // file_path);
// // unlink_file_and_test(file_path);
// // #endif
// //
// // file_path = create_file_and_test(mount_location,
// // "write_fails_ro_test"); test_write_operations_fail_if_read_only(
// // utils::path::create_api_path("write_fails_ro_test"),
// // file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path = create_file_and_test(mount_location, "getattr.txt");
// // test_file_getattr(utils::path::create_api_path("getattr.txt"),
// // file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path = utils::path::combine(mount_location, {"getattr_dir"});
// // test_directory_getattr(utils::path::create_api_path("getattr_dir"),
// // file_path);
// // rmdir_and_test(file_path);
// //
// // #if !__APPLE__ && HAS_SETXATTR
// // file_path =
// // create_file_and_test(mount_location,
// // "xattr_invalid_names_test");
// // test_xattr_invalid_parameters(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path =
// // create_file_and_test(mount_location, "xattr_create_get_test");
// // test_xattr_create_and_get(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path =
// // create_file_and_test(mount_location, "xattr_listxattr_test");
// // test_xattr_listxattr(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path = create_file_and_test(mount_location,
// // "xattr_replace_test"); test_xattr_replace(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path =
// // create_file_and_test(mount_location,
// // "xattr_default_create_test");
// // test_xattr_default_create(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path =
// // create_file_and_test(mount_location,
// // "xattr_default_replace_test");
// // test_xattr_default_replace(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path = create_file_and_test(mount_location,
// // "xattr_create_fails_exists_test");
// // test_xattr_create_fails_if_exists(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path = create_file_and_test(mount_location,
// // "xattr_create_fails_not_exists_test");
// // test_xattr_create_fails_if_not_exists(file_path);
// // unlink_file_and_test(file_path);
// //
// // file_path =
// // create_file_and_test(mount_location,
// "xattr_removexattr_test");
// // test_xattr_removexattr(file_path);
// // unlink_file_and_test(file_path);
// // #endif

View File

@@ -0,0 +1,248 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "types/remote.hpp"
#include "types/repertory.hpp"
namespace repertory {
TEST(json_serialize, can_handle_directory_item) {
directory_item cfg{
"api", "parent", true, 2U, {{META_DIRECTORY, "true"}},
};
json data(cfg);
EXPECT_STREQ("api", data.at(JSON_API_PATH).get<std::string>().c_str());
EXPECT_STREQ("parent", data.at(JSON_API_PARENT).get<std::string>().c_str());
EXPECT_TRUE(data.at(JSON_DIRECTORY).get<bool>());
EXPECT_STREQ(
"true", data.at(JSON_META).at(META_DIRECTORY).get<std::string>().c_str());
{
auto cfg2 = data.get<directory_item>();
EXPECT_STREQ(cfg2.api_path.c_str(), cfg.api_path.c_str());
EXPECT_STREQ(cfg2.api_parent.c_str(), cfg.api_parent.c_str());
EXPECT_EQ(cfg2.directory, cfg.directory);
EXPECT_STREQ(cfg2.meta.at(META_DIRECTORY).c_str(),
cfg.meta.at(META_DIRECTORY).c_str());
}
}
TEST(json_serialize, can_handle_encrypt_config) {
encrypt_config cfg{
"token",
"path",
};
json data(cfg);
EXPECT_STREQ("token",
data.at(JSON_ENCRYPTION_TOKEN).get<std::string>().c_str());
EXPECT_STREQ("path", data.at(JSON_PATH).get<std::string>().c_str());
{
auto cfg2 = data.get<encrypt_config>();
EXPECT_STREQ(cfg2.encryption_token.c_str(), cfg.encryption_token.c_str());
EXPECT_STREQ(cfg2.path.c_str(), cfg.path.c_str());
}
}
TEST(json_serialize, can_handle_host_config) {
host_config cfg{
"agent", "pwd", "user", 1024U, "host", "path", "http", 11U,
};
json data(cfg);
EXPECT_STREQ("agent", data.at(JSON_AGENT_STRING).get<std::string>().c_str());
EXPECT_STREQ("pwd", data.at(JSON_API_PASSWORD).get<std::string>().c_str());
EXPECT_STREQ("user", data.at(JSON_API_USER).get<std::string>().c_str());
EXPECT_EQ(1024U, data.at(JSON_API_PORT).get<std::uint16_t>());
EXPECT_STREQ("host",
data.at(JSON_HOST_NAME_OR_IP).get<std::string>().c_str());
EXPECT_STREQ("path", data.at(JSON_PATH).get<std::string>().c_str());
EXPECT_STREQ("http", data.at(JSON_PROTOCOL).get<std::string>().c_str());
EXPECT_EQ(11U, data.at(JSON_TIMEOUT_MS).get<std::uint16_t>());
{
auto cfg2 = data.get<host_config>();
EXPECT_STREQ(cfg2.agent_string.c_str(), cfg.agent_string.c_str());
EXPECT_STREQ(cfg2.api_password.c_str(), cfg.api_password.c_str());
EXPECT_STREQ(cfg2.api_user.c_str(), cfg.api_user.c_str());
EXPECT_EQ(cfg2.api_port, cfg.api_port);
EXPECT_STREQ(cfg2.host_name_or_ip.c_str(), cfg.host_name_or_ip.c_str());
EXPECT_STREQ(cfg2.path.c_str(), cfg.path.c_str());
EXPECT_STREQ(cfg2.protocol.c_str(), cfg.protocol.c_str());
EXPECT_EQ(cfg2.timeout_ms, cfg.timeout_ms);
}
}
TEST(json_serialize, can_handle_remote_config) {
remote::remote_config cfg{
1024U, "token", "host", 11U, 20U, 21U,
};
json data(cfg);
EXPECT_EQ(1024U, data.at(JSON_API_PORT).get<std::uint16_t>());
EXPECT_STREQ("token",
data.at(JSON_ENCRYPTION_TOKEN).get<std::string>().c_str());
EXPECT_STREQ("host",
data.at(JSON_HOST_NAME_OR_IP).get<std::string>().c_str());
EXPECT_EQ(11U, data.at(JSON_MAX_CONNECTIONS).get<std::uint16_t>());
EXPECT_EQ(20U, data.at(JSON_RECV_TIMEOUT_MS).get<std::uint32_t>());
EXPECT_EQ(21U, data.at(JSON_SEND_TIMEOUT_MS).get<std::uint32_t>());
{
auto cfg2 = data.get<remote::remote_config>();
EXPECT_EQ(cfg2.api_port, cfg.api_port);
EXPECT_STREQ(cfg2.encryption_token.c_str(), cfg.encryption_token.c_str());
EXPECT_STREQ(cfg2.host_name_or_ip.c_str(), cfg.host_name_or_ip.c_str());
EXPECT_EQ(cfg2.max_connections, cfg.max_connections);
EXPECT_EQ(cfg2.recv_timeout_ms, cfg.recv_timeout_ms);
EXPECT_EQ(cfg2.send_timeout_ms, cfg.send_timeout_ms);
}
}
TEST(json_serialize, can_handle_remote_mount) {
remote::remote_mount cfg{1024U, 21U, true, "token"};
json data(cfg);
EXPECT_EQ(1024U, data.at(JSON_API_PORT).get<std::uint16_t>());
EXPECT_EQ(21U, data.at(JSON_CLIENT_POOL_SIZE).get<std::uint16_t>());
EXPECT_TRUE(data.at(JSON_ENABLE_REMOTE_MOUNT).get<bool>());
EXPECT_STREQ("token",
data.at(JSON_ENCRYPTION_TOKEN).get<std::string>().c_str());
{
auto cfg2 = data.get<remote::remote_mount>();
EXPECT_EQ(cfg2.api_port, cfg.api_port);
EXPECT_EQ(cfg2.client_pool_size, cfg.client_pool_size);
EXPECT_EQ(cfg2.enable, cfg.enable);
EXPECT_STREQ(cfg2.encryption_token.c_str(), cfg.encryption_token.c_str());
}
}
TEST(json_serialize, can_handle_s3_config) {
s3_config cfg{
"access", "bucket", "token", "region", "secret", 31U, "url", true, false,
};
json data(cfg);
EXPECT_STREQ("access", data.at(JSON_ACCESS_KEY).get<std::string>().c_str());
EXPECT_STREQ("bucket", data.at(JSON_BUCKET).get<std::string>().c_str());
EXPECT_STREQ("token",
data.at(JSON_ENCRYPTION_TOKEN).get<std::string>().c_str());
EXPECT_STREQ("region", data.at(JSON_REGION).get<std::string>().c_str());
EXPECT_STREQ("secret", data.at(JSON_SECRET_KEY).get<std::string>().c_str());
EXPECT_EQ(31U, data.at(JSON_TIMEOUT_MS).get<std::uint32_t>());
EXPECT_STREQ("url", data.at(JSON_URL).get<std::string>().c_str());
EXPECT_TRUE(data.at(JSON_USE_PATH_STYLE).get<bool>());
EXPECT_FALSE(data.at(JSON_USE_REGION_IN_URL).get<bool>());
{
auto cfg2 = data.get<s3_config>();
EXPECT_STREQ(cfg2.access_key.c_str(), cfg.access_key.c_str());
EXPECT_STREQ(cfg2.bucket.c_str(), cfg.bucket.c_str());
EXPECT_STREQ(cfg2.encryption_token.c_str(), cfg.encryption_token.c_str());
EXPECT_STREQ(cfg2.region.c_str(), cfg.region.c_str());
EXPECT_STREQ(cfg2.secret_key.c_str(), cfg.secret_key.c_str());
EXPECT_EQ(cfg2.timeout_ms, cfg.timeout_ms);
EXPECT_STREQ(cfg2.url.c_str(), cfg.url.c_str());
EXPECT_EQ(cfg2.use_path_style, cfg.use_path_style);
EXPECT_EQ(cfg2.use_region_in_url, cfg.use_region_in_url);
}
}
TEST(json_serialize, can_handle_sia_config) {
sia_config cfg{
"bucket",
};
json data(cfg);
EXPECT_STREQ("bucket", data.at(JSON_BUCKET).get<std::string>().c_str());
{
auto cfg2 = data.get<sia_config>();
EXPECT_STREQ(cfg2.bucket.c_str(), cfg.bucket.c_str());
}
}
TEST(json_serialize, can_handle_atomic) {
atomic<sia_config> cfg({
"bucket",
});
json data(cfg);
EXPECT_STREQ("bucket", data.at(JSON_BUCKET).get<std::string>().c_str());
{
auto cfg2 = data.get<atomic<sia_config>>();
EXPECT_STREQ(cfg2.load().bucket.c_str(), cfg.load().bucket.c_str());
}
}
TEST(json_serialize, can_handle_database_type) {
json data(database_type::rocksdb);
EXPECT_EQ(database_type::rocksdb, data.get<database_type>());
EXPECT_STREQ("rocksdb", data.get<std::string>().c_str());
data = database_type::sqlite;
EXPECT_EQ(database_type::sqlite, data.get<database_type>());
EXPECT_STREQ("sqlite", data.get<std::string>().c_str());
}
TEST(json_serialize, can_handle_download_type) {
json data(download_type::direct);
EXPECT_EQ(download_type::direct, data.get<download_type>());
EXPECT_STREQ("direct", data.get<std::string>().c_str());
data = download_type::default_;
EXPECT_EQ(download_type::default_, data.get<download_type>());
EXPECT_STREQ("default", data.get<std::string>().c_str());
data = download_type::ring_buffer;
EXPECT_EQ(download_type::ring_buffer, data.get<download_type>());
EXPECT_STREQ("ring_buffer", data.get<std::string>().c_str());
}
TEST(json_serialize, can_handle_atomic_database_type) {
json data(atomic<database_type>{database_type::rocksdb});
EXPECT_EQ(database_type::rocksdb, data.get<atomic<database_type>>());
EXPECT_STREQ("rocksdb", data.get<std::string>().c_str());
data = atomic<database_type>(database_type::sqlite);
EXPECT_EQ(database_type::sqlite, data.get<atomic<database_type>>());
EXPECT_STREQ("sqlite", data.get<std::string>().c_str());
}
TEST(json_serialize, can_handle_atomic_download_type) {
json data(atomic<download_type>{download_type::direct});
EXPECT_EQ(download_type::direct, data.get<atomic<download_type>>());
EXPECT_STREQ("direct", data.get<std::string>().c_str());
data = atomic<download_type>{download_type::default_};
EXPECT_EQ(download_type::default_, data.get<download_type>());
EXPECT_STREQ("default", data.get<std::string>().c_str());
data = atomic<download_type>{download_type::ring_buffer};
EXPECT_EQ(download_type::ring_buffer, data.get<atomic<download_type>>());
EXPECT_STREQ("ring_buffer", data.get<std::string>().c_str());
}
} // namespace repertory

View File

@@ -0,0 +1,104 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "platform/platform.hpp"
namespace repertory {
TEST(lock_data, lock_and_unlock) {
{
lock_data l(provider_type::sia, "1");
EXPECT_EQ(lock_result::success, l.grab_lock());
std::thread([]() {
lock_data l2(provider_type::sia, "1");
EXPECT_EQ(lock_result::locked, l2.grab_lock(10));
}).join();
}
std::thread([]() {
lock_data l(provider_type::sia, "1");
EXPECT_EQ(lock_result::success, l.grab_lock(10));
}).join();
#if defined(_WIN32)
lock_data l2(provider_type::remote, "1");
EXPECT_EQ(lock_result::success, l2.grab_lock());
lock_data l3(provider_type::remote, "2");
EXPECT_EQ(lock_result::success, l3.grab_lock());
#endif
}
#if defined(_WIN32)
TEST(lock_data, set_and_unset_mount_state) {
lock_data l(provider_type::sia, "1");
EXPECT_TRUE(l.set_mount_state(true, "C:", 99));
lock_data l2(provider_type::remote, "1");
EXPECT_TRUE(l2.set_mount_state(true, "D:", 97));
lock_data l3(provider_type::remote, "2");
EXPECT_TRUE(l3.set_mount_state(true, "E:", 96));
json mount_state;
EXPECT_TRUE(l.get_mount_state(mount_state));
EXPECT_STREQ(R"({"Active":true,"Location":"C:","PID":99})",
mount_state["Sia1"].dump().c_str());
EXPECT_STREQ(R"({"Active":true,"Location":"D:","PID":97})",
mount_state["Remote1"].dump().c_str());
EXPECT_STREQ(R"({"Active":true,"Location":"E:","PID":96})",
mount_state["Remote2"].dump().c_str());
EXPECT_TRUE(l.set_mount_state(false, "C:", 99));
EXPECT_TRUE(l2.set_mount_state(false, "D:", 98));
EXPECT_TRUE(l3.set_mount_state(false, "E:", 97));
EXPECT_TRUE(l.get_mount_state(mount_state));
EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})",
mount_state["Sia1"].dump().c_str());
EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})",
mount_state["Remote1"].dump().c_str());
EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})",
mount_state["Remote2"].dump().c_str());
}
#else
TEST(lock_data, set_and_unset_mount_state) {
lock_data l(provider_type::sia, "1");
EXPECT_TRUE(l.set_mount_state(true, "/mnt/1", 99));
json mount_state;
EXPECT_TRUE(l.get_mount_state(mount_state));
EXPECT_STREQ(R"({"Active":true,"Location":"/mnt/1","PID":99})",
mount_state["Sia1"].dump().c_str());
EXPECT_TRUE(l.set_mount_state(false, "/mnt/1", 99));
EXPECT_TRUE(l.get_mount_state(mount_state));
EXPECT_STREQ(R"({"Active":false,"Location":"","PID":-1})",
mount_state["Sia1"].dump().c_str());
}
#endif
} // namespace repertory

View File

@@ -0,0 +1,618 @@
/*
Copyright <2018-2024> <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.
*/
#include "fixtures/meta_db_fixture.hpp"
namespace {
[[nodiscard]] auto create_test_file() -> std::string {
static std::atomic<std::uint64_t> idx{};
return "/test" + std::to_string(++idx);
}
} // namespace
namespace repertory {
TYPED_TEST_CASE(meta_db_test, meta_db_types);
TYPED_TEST(meta_db_test, can_get_api_path_from_source_path) {
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
}));
std::string api_path;
EXPECT_EQ(api_error::success,
this->meta_db->get_api_path(test_source, api_path));
EXPECT_STREQ(test_file.c_str(), api_path.c_str());
}
TYPED_TEST(meta_db_test, can_change_source_path) {
auto test_file = create_test_file();
auto test_source = create_test_file();
auto test_source2 = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
}));
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(test_file, META_SOURCE, test_source2));
std::string api_path;
EXPECT_EQ(api_error::success,
this->meta_db->get_api_path(test_source2, api_path));
EXPECT_STREQ(test_file.c_str(), api_path.c_str());
std::string api_path2;
EXPECT_EQ(api_error::item_not_found,
this->meta_db->get_api_path(test_source, api_path2));
EXPECT_TRUE(api_path2.empty());
}
TYPED_TEST(meta_db_test,
get_api_path_returns_item_not_found_if_source_does_not_exist) {
std::string api_path;
EXPECT_EQ(api_error::item_not_found,
this->meta_db->get_api_path(create_test_file(), api_path));
EXPECT_TRUE(api_path.empty());
}
TYPED_TEST(meta_db_test, can_get_api_file_list) {
std::vector<std::string> directories{};
for (auto idx = 0U; idx < 5U; ++idx) {
auto test_dir = create_test_file();
directories.push_back(test_dir);
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
}));
}
std::vector<std::string> files{};
for (auto idx = 0U; idx < 5U; ++idx) {
auto test_file = create_test_file();
files.push_back(test_file);
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
}));
}
auto file_list = this->meta_db->get_api_path_list();
for (const auto &api_path : directories) {
EXPECT_TRUE(utils::collection::includes(file_list, api_path));
}
for (const auto &api_path : files) {
EXPECT_TRUE(utils::collection::includes(file_list, api_path));
}
}
TYPED_TEST(meta_db_test,
full_get_item_meta_returns_item_not_found_if_item_does_not_exist) {
auto api_path = create_test_file();
api_meta_map meta;
EXPECT_EQ(api_error::item_not_found,
this->meta_db->get_item_meta(api_path, meta));
EXPECT_TRUE(meta.empty());
}
TYPED_TEST(
meta_db_test,
individual_get_item_meta_returns_item_not_found_if_item_does_not_exist) {
auto api_path = create_test_file();
std::string value;
EXPECT_EQ(api_error::item_not_found,
this->meta_db->get_item_meta(api_path, META_DIRECTORY, value));
EXPECT_TRUE(value.empty());
}
TYPED_TEST(meta_db_test, can_get_full_item_meta_for_directory) {
auto api_path = create_test_file();
auto source_path = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
api_path, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_PINNED, utils::string::from_bool(true)},
{META_SIZE, std::to_string(2ULL)},
{META_SOURCE, source_path},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(api_path, meta));
EXPECT_TRUE(utils::string::to_bool(meta[META_DIRECTORY]));
EXPECT_FALSE(utils::string::to_bool(meta[META_PINNED]));
EXPECT_EQ(0U, utils::string::to_uint64(meta[META_SIZE]));
EXPECT_TRUE(meta[META_SOURCE].empty());
}
TYPED_TEST(meta_db_test, can_get_full_item_meta_for_file) {
auto api_path = create_test_file();
auto source_path = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
api_path, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_PINNED, utils::string::from_bool(true)},
{META_SIZE, std::to_string(2ULL)},
{META_SOURCE, source_path},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(api_path, meta));
EXPECT_FALSE(utils::string::to_bool(meta[META_DIRECTORY]));
EXPECT_TRUE(utils::string::to_bool(meta[META_PINNED]));
EXPECT_EQ(2ULL, utils::string::to_uint64(meta[META_SIZE]));
EXPECT_STREQ(source_path.c_str(), meta[META_SOURCE].c_str());
}
TYPED_TEST(meta_db_test, can_get_individual_item_meta_for_directory) {
auto api_path = create_test_file();
auto source_path = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
api_path, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_PINNED, utils::string::from_bool(true)},
{META_SIZE, std::to_string(2ULL)},
{META_SOURCE, source_path},
}));
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_DIRECTORY, value));
EXPECT_TRUE(utils::string::to_bool(value));
}
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_PINNED, value));
EXPECT_FALSE(utils::string::to_bool(value));
}
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_SIZE, value));
EXPECT_EQ(0ULL, utils::string::to_uint64(value));
}
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_SOURCE, value));
EXPECT_TRUE(value.empty());
}
}
TYPED_TEST(meta_db_test, can_get_individual_item_meta_for_file) {
auto api_path = create_test_file();
auto source_path = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
api_path, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_PINNED, utils::string::from_bool(true)},
{META_SIZE, std::to_string(2ULL)},
{META_SOURCE, source_path},
}));
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_DIRECTORY, value));
EXPECT_FALSE(utils::string::to_bool(value));
}
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_PINNED, value));
EXPECT_TRUE(utils::string::to_bool(value));
}
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_SIZE, value));
EXPECT_EQ(2ULL, utils::string::to_uint64(value));
}
{
std::string value;
EXPECT_EQ(api_error::success,
this->meta_db->get_item_meta(api_path, META_SOURCE, value));
EXPECT_STREQ(source_path.c_str(), value.c_str());
}
}
TYPED_TEST(meta_db_test, can_get_pinned_files) {
std::vector<std::string> pinned_files{};
std::vector<std::string> unpinned_files{};
for (auto idx = 0U; idx < 20U; ++idx) {
auto test_file = create_test_file();
auto pinned = idx % 2U == 0U;
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_PINNED, utils::string::from_bool(pinned)},
}));
if (pinned) {
pinned_files.push_back(test_file);
continue;
}
unpinned_files.push_back(test_file);
}
auto pinned = this->meta_db->get_pinned_files();
EXPECT_GE(pinned.size(), pinned_files.size());
for (const auto &api_path : pinned_files) {
EXPECT_TRUE(utils::collection::includes(pinned, api_path));
}
for (const auto &api_path : unpinned_files) {
EXPECT_TRUE(utils::collection::excludes(pinned, api_path));
}
}
TYPED_TEST(meta_db_test, can_get_total_item_count) {
this->meta_db->clear();
EXPECT_EQ(0U, this->meta_db->get_total_item_count());
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
}));
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
}));
EXPECT_EQ(2U, this->meta_db->get_total_item_count());
}
TYPED_TEST(meta_db_test,
get_total_item_count_decreases_after_directory_is_removed) {
this->meta_db->clear();
EXPECT_EQ(0U, this->meta_db->get_total_item_count());
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
}));
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
}));
this->meta_db->remove_api_path(test_dir);
EXPECT_EQ(1U, this->meta_db->get_total_item_count());
}
TYPED_TEST(meta_db_test, get_total_item_count_decreases_after_file_is_removed) {
this->meta_db->clear();
EXPECT_EQ(0U, this->meta_db->get_total_item_count());
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
}));
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
}));
this->meta_db->remove_api_path(test_file);
EXPECT_EQ(1U, this->meta_db->get_total_item_count());
}
TYPED_TEST(meta_db_test, can_get_total_size) {
this->meta_db->clear();
EXPECT_EQ(0U, this->meta_db->get_total_item_count());
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "2"},
}));
test_file = create_test_file();
test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "1"},
}));
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_SIZE, "7"},
}));
EXPECT_EQ(3U, this->meta_db->get_total_size());
}
TYPED_TEST(meta_db_test,
total_size_does_not_decrease_after_directory_is_removed) {
this->meta_db->clear();
EXPECT_EQ(0U, this->meta_db->get_total_item_count());
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "2"},
}));
test_file = create_test_file();
test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "1"},
}));
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_SIZE, "7"},
}));
this->meta_db->remove_api_path(test_dir);
EXPECT_EQ(3U, this->meta_db->get_total_size());
}
TYPED_TEST(meta_db_test, total_size_decreases_after_file_is_removed) {
this->meta_db->clear();
EXPECT_EQ(0U, this->meta_db->get_total_item_count());
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "2"},
}));
test_file = create_test_file();
test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "1"},
}));
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_SIZE, "7"},
}));
this->meta_db->remove_api_path(test_file);
EXPECT_EQ(2U, this->meta_db->get_total_size());
}
TYPED_TEST(meta_db_test, can_remove_api_path) {
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "2"},
}));
this->meta_db->remove_api_path(test_file);
api_meta_map meta;
EXPECT_EQ(api_error::item_not_found,
this->meta_db->get_item_meta(test_file, meta));
}
TYPED_TEST(meta_db_test, can_rename_item_meta) {
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
{META_SOURCE, test_source},
{META_SIZE, "2"},
}));
auto test_file2 = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->rename_item_meta(test_file, test_file2));
api_meta_map meta;
EXPECT_EQ(api_error::item_not_found,
this->meta_db->get_item_meta(test_file, meta));
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(test_file2, meta));
}
TYPED_TEST(meta_db_test, rename_item_meta_fails_if_not_found) {
auto test_file = create_test_file();
auto test_file2 = create_test_file();
EXPECT_EQ(api_error::item_not_found,
this->meta_db->rename_item_meta(test_file, test_file2));
}
TYPED_TEST(meta_db_test, set_item_meta_fails_with_missing_directory_meta) {
auto test_file = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(api_error::error, this->meta_db->set_item_meta(
test_file, {
{META_SOURCE, test_source},
}));
EXPECT_EQ(api_error::error,
this->meta_db->set_item_meta(test_file, META_SOURCE, test_source));
}
TYPED_TEST(meta_db_test, check_size_is_ignored_for_directory) {
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_SIZE, "2"},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(test_dir, meta));
EXPECT_EQ(0U, utils::string::to_uint64(meta[META_SIZE]));
}
TYPED_TEST(meta_db_test, check_pinned_is_ignored_for_directory) {
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_PINNED, utils::string::from_bool(true)},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(test_dir, meta));
EXPECT_FALSE(utils::string::to_bool(meta[META_PINNED]));
}
TYPED_TEST(meta_db_test, check_source_is_ignored_for_directory) {
auto test_dir = create_test_file();
auto test_source = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
{META_SOURCE, test_source},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(test_dir, meta));
EXPECT_TRUE(meta[META_SOURCE].empty());
}
TYPED_TEST(meta_db_test, check_set_item_meta_directory_defaults) {
auto test_dir = create_test_file();
EXPECT_EQ(api_error::success,
this->meta_db->set_item_meta(
test_dir, {
{META_DIRECTORY, utils::string::from_bool(true)},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(test_dir, meta));
EXPECT_TRUE(utils::string::to_bool(meta[META_DIRECTORY]));
EXPECT_FALSE(utils::string::to_bool(meta[META_PINNED]));
EXPECT_EQ(0U, utils::string::to_uint64(meta[META_SIZE]));
EXPECT_TRUE(meta[META_SOURCE].empty());
}
TYPED_TEST(meta_db_test, check_set_item_meta_file_defaults) {
auto test_file = create_test_file();
EXPECT_EQ(
api_error::success,
this->meta_db->set_item_meta(
test_file, {
{META_DIRECTORY, utils::string::from_bool(false)},
}));
api_meta_map meta;
EXPECT_EQ(api_error::success, this->meta_db->get_item_meta(test_file, meta));
EXPECT_FALSE(utils::string::to_bool(meta[META_DIRECTORY]));
EXPECT_FALSE(utils::string::to_bool(meta[META_PINNED]));
EXPECT_EQ(0U, utils::string::to_uint64(meta[META_SIZE]));
EXPECT_TRUE(meta[META_SOURCE].empty());
}
} // namespace repertory

View File

@@ -0,0 +1,720 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "app_config.hpp"
#include "file_manager/cache_size_mgr.hpp"
#include "file_manager/open_file.hpp"
#include "mocks/mock_provider.hpp"
#include "mocks/mock_upload_manager.hpp"
#include "types/repertory.hpp"
#include "utils/event_capture.hpp"
#include "utils/path.hpp"
namespace {
constexpr const std::size_t test_chunk_size{1024U};
}
namespace repertory {
class open_file_test : public ::testing::Test {
public:
console_consumer con_consumer;
std::unique_ptr<app_config> cfg;
static std::atomic<std::size_t> inst;
mock_provider provider;
mock_upload_manager upload_mgr;
protected:
void SetUp() override {
event_system::instance().start();
auto open_file_dir = repertory::utils::path::combine(
repertory::test::get_test_output_dir(),
{"open_file_test" + std::to_string(++inst)});
cfg = std::make_unique<app_config>(provider_type::sia, open_file_dir);
cache_size_mgr::instance().initialize(cfg.get());
}
void TearDown() override { event_system::instance().stop(); }
};
std::atomic<std::size_t> open_file_test::inst{0U};
static void test_closeable_open_file(const open_file &file, bool directory,
const api_error &err, std::uint64_t size,
const std::string &source_path) {
EXPECT_EQ(directory, file.is_directory());
EXPECT_EQ(err, file.get_api_error());
EXPECT_EQ(std::size_t(0U), file.get_open_file_count());
EXPECT_EQ(std::uint64_t(size), file.get_file_size());
EXPECT_STREQ(source_path.c_str(), file.get_source_path().c_str());
EXPECT_TRUE(file.can_close());
}
static void validate_write(open_file &file, std::size_t offset,
data_buffer data, std::size_t bytes_written) {
EXPECT_EQ(data.size(), bytes_written);
data_buffer read_data{};
EXPECT_EQ(api_error::success, file.read(data.size(), offset, read_data));
EXPECT_TRUE(std::equal(data.begin(), data.end(), read_data.begin()));
}
TEST_F(open_file_test, properly_initializes_state_for_0_byte_file) {
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = 0U;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
EXPECT_EQ(std::size_t(0U), file.get_read_state().size());
EXPECT_FALSE(file.is_modified());
EXPECT_EQ(test_chunk_size, file.get_chunk_size());
}
TEST_F(open_file_test, properly_initializes_state_based_on_chunk_size) {
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = 8U;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
open_file file(1U, 0U, fsi, provider, upload_mgr);
EXPECT_EQ(std::size_t(8U), file.get_read_state().size());
EXPECT_TRUE(file.get_read_state().none());
EXPECT_FALSE(file.is_modified());
EXPECT_CALL(provider, set_item_meta(fsi.api_path, META_SOURCE, _))
.WillOnce(Return(api_error::success));
EXPECT_EQ(std::size_t(1U), file.get_chunk_size());
}
TEST_F(open_file_test, will_not_change_source_path_for_0_byte_file) {
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = 0U;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
open_file file(0U, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, 0U, source_path);
file.close();
EXPECT_EQ(api_error::success, file.get_api_error());
EXPECT_STREQ(source_path.c_str(), file.get_source_path().c_str());
EXPECT_TRUE(utils::file::file(fsi.source_path).exists());
}
TEST_F(open_file_test, will_change_source_path_if_file_size_is_greater_than_0) {
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
EXPECT_CALL(provider, set_item_meta(fsi.api_path, META_SOURCE, _))
.WillOnce([&fsi](const std::string &, const std::string &,
const std::string &source_path2) -> api_error {
EXPECT_STRNE(fsi.source_path.c_str(), source_path2.c_str());
return api_error::success;
});
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, test_chunk_size,
source_path);
file.close();
EXPECT_EQ(api_error::download_stopped, file.get_api_error());
EXPECT_STRNE(source_path.c_str(), file.get_source_path().c_str());
EXPECT_FALSE(utils::file::file(source_path).exists());
}
TEST_F(open_file_test,
will_not_change_source_path_if_file_size_matches_existing_source) {
auto &rf = test::create_random_file(test_chunk_size);
const auto source_path = rf.get_path();
rf.close();
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, test_chunk_size,
source_path);
file.close();
EXPECT_EQ(api_error::success, file.get_api_error());
EXPECT_STREQ(source_path.c_str(), file.get_source_path().c_str());
EXPECT_TRUE(utils::file::file(source_path).exists());
}
TEST_F(open_file_test, write_with_incomplete_download) {
const auto source_path = test::generate_test_file_name("test");
auto &nf = test::create_random_file(test_chunk_size * 2u);
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 2u;
fsi.source_path = source_path;
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success,
test_chunk_size * 2u, source_path);
EXPECT_CALL(provider, set_item_meta(fsi.api_path, _))
.WillOnce([](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
return api_error::success;
});
EXPECT_CALL(provider, read_file_bytes)
.WillRepeatedly([&nf](const std::string & /* api_path */,
std::size_t size, std::uint64_t offset,
data_buffer &data,
stop_type &stop_requested) -> api_error {
if (stop_requested) {
return api_error::download_stopped;
}
if (offset == 0U) {
std::size_t bytes_read{};
data.resize(size);
auto ret = nf.read(data, offset, &bytes_read) ? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
}
while (not stop_requested) {
std::this_thread::sleep_for(100ms);
}
return api_error::download_stopped;
});
EXPECT_CALL(upload_mgr, remove_upload)
.WillOnce([&fsi](const std::string &api_path) {
EXPECT_EQ(fsi.api_path, api_path);
});
EXPECT_CALL(upload_mgr, store_resume)
.Times(2)
.WillRepeatedly([&fsi](const i_open_file &cur_file) {
EXPECT_EQ(fsi.api_path, cur_file.get_api_path());
EXPECT_EQ(fsi.source_path, cur_file.get_source_path());
});
data_buffer data = {10, 9, 8};
std::size_t bytes_written{};
EXPECT_EQ(api_error::success, file.write(0U, data, bytes_written));
validate_write(file, 0U, data, bytes_written);
const auto test_state = [&]() {
EXPECT_STREQ(source_path.c_str(), file.get_source_path().c_str());
EXPECT_FALSE(file.can_close());
EXPECT_TRUE(file.is_modified());
EXPECT_TRUE(file.get_read_state(0U));
EXPECT_FALSE(file.get_read_state(1u));
};
test_state();
file.close();
nf.close();
test_state();
EXPECT_EQ(api_error::download_incomplete, file.get_api_error());
EXPECT_TRUE(utils::file::file(fsi.source_path).exists());
}
TEST_F(open_file_test, write_new_file) {
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = 0U;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, store_resume)
.WillOnce([&fsi](const i_open_file &file) {
EXPECT_EQ(fsi.api_path, file.get_api_path());
EXPECT_EQ(fsi.source_path, file.get_source_path());
});
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, 0U, source_path);
data_buffer data = {10, 9, 8};
EXPECT_CALL(provider, set_item_meta(fsi.api_path, _))
.WillOnce([&data](const std::string &,
const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_SIZE).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
EXPECT_EQ(data.size(), utils::string::to_size_t(meta.at(META_SIZE)));
return api_error::success;
})
.WillOnce([](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
return api_error::success;
});
EXPECT_CALL(upload_mgr, remove_upload)
.WillOnce([&fsi](const std::string &api_path) {
EXPECT_EQ(fsi.api_path, api_path);
});
EXPECT_CALL(upload_mgr, queue_upload)
.WillOnce([&fsi](const i_open_file &cur_file) {
EXPECT_EQ(fsi.api_path, cur_file.get_api_path());
EXPECT_EQ(fsi.source_path, cur_file.get_source_path());
});
std::size_t bytes_written{};
EXPECT_EQ(api_error::success, file.write(0U, data, bytes_written));
const auto test_state = [&]() {
EXPECT_STREQ(source_path.c_str(), file.get_source_path().c_str());
EXPECT_FALSE(file.can_close());
EXPECT_TRUE(file.is_modified());
EXPECT_TRUE(file.get_read_state(0U));
EXPECT_EQ(std::size_t(1u), file.get_read_state().size());
EXPECT_EQ(data.size(), file.get_file_size());
};
test_state();
file.close();
test_state();
EXPECT_EQ(api_error::success, file.get_api_error());
EXPECT_TRUE(utils::file::file(fsi.source_path).exists());
}
TEST_F(open_file_test, write_new_file_multiple_chunks) {
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = 0U;
fsi.source_path = source_path;
EXPECT_CALL(upload_mgr, store_resume)
.WillOnce([&fsi](const i_open_file &file) {
EXPECT_EQ(fsi.api_path, file.get_api_path());
EXPECT_EQ(fsi.source_path, file.get_source_path());
});
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, 0U, source_path);
data_buffer data = {10, 9, 8};
EXPECT_CALL(provider, set_item_meta(fsi.api_path, _))
.WillOnce([&data](const std::string &,
const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_SIZE).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
EXPECT_EQ(data.size(), utils::string::to_size_t(meta.at(META_SIZE)));
return api_error::success;
})
.WillOnce([](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
return api_error::success;
})
.WillOnce(
[&data](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_SIZE).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
EXPECT_EQ(data.size() + test_chunk_size,
utils::string::to_size_t(meta.at(META_SIZE)));
return api_error::success;
})
.WillOnce([](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
return api_error::success;
});
EXPECT_CALL(upload_mgr, remove_upload)
.WillOnce([&fsi](const std::string &api_path) {
EXPECT_EQ(fsi.api_path, api_path);
});
EXPECT_CALL(upload_mgr, queue_upload)
.WillOnce([&fsi](const i_open_file &cur_file) {
EXPECT_EQ(fsi.api_path, cur_file.get_api_path());
EXPECT_EQ(fsi.source_path, cur_file.get_source_path());
});
std::size_t bytes_written{};
EXPECT_EQ(api_error::success, file.write(0U, data, bytes_written));
EXPECT_EQ(api_error::success,
file.write(test_chunk_size, data, bytes_written));
const auto test_state = [&]() {
EXPECT_STREQ(source_path.c_str(), file.get_source_path().c_str());
EXPECT_FALSE(file.can_close());
EXPECT_TRUE(file.is_modified());
EXPECT_EQ(std::size_t(2u), file.get_read_state().size());
for (std::size_t i = 0U; i < 2u; i++) {
EXPECT_TRUE(file.get_read_state(i));
}
EXPECT_EQ(data.size() + test_chunk_size, file.get_file_size());
};
test_state();
file.close();
test_state();
EXPECT_EQ(api_error::success, file.get_api_error());
EXPECT_TRUE(utils::file::file(fsi.source_path).exists());
}
TEST_F(open_file_test, resize_file_to_0_bytes) {
auto &r_file = test::create_random_file(test_chunk_size * 4U);
const auto source_path = r_file.get_path();
r_file.close();
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4U;
fsi.source_path = source_path;
EXPECT_EQ(api_error::success, cache_size_mgr::instance().expand(fsi.size));
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, fsi.size,
source_path);
EXPECT_CALL(provider, set_item_meta(fsi.api_path, _))
.WillOnce([](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_SIZE).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
EXPECT_EQ(std::size_t(0U),
utils::string::to_size_t(meta.at(META_SIZE)));
return api_error::success;
});
EXPECT_CALL(upload_mgr, remove_upload)
.WillOnce([&fsi](const std ::string &api_path) {
EXPECT_EQ(fsi.api_path, api_path);
});
EXPECT_CALL(upload_mgr, queue_upload)
.WillOnce([&fsi](const i_open_file &cur_file) {
EXPECT_EQ(fsi.api_path, cur_file.get_api_path());
EXPECT_EQ(fsi.source_path, cur_file.get_source_path());
});
EXPECT_CALL(upload_mgr, store_resume)
.WillOnce([&fsi](const i_open_file &cur_file) {
EXPECT_EQ(fsi.api_path, cur_file.get_api_path());
EXPECT_EQ(fsi.source_path, cur_file.get_source_path());
});
EXPECT_EQ(api_error::success, file.resize(0U));
EXPECT_EQ(std::size_t(0U), file.get_file_size());
EXPECT_FALSE(file.can_close());
EXPECT_TRUE(file.is_modified());
EXPECT_EQ(std::size_t(0U), file.get_read_state().size());
}
TEST_F(open_file_test, resize_file_by_full_chunk) {
auto &r_file = test::create_random_file(test_chunk_size * 4U);
const auto source_path = r_file.get_path();
r_file.close();
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4U;
fsi.source_path = source_path;
EXPECT_EQ(api_error::success, cache_size_mgr::instance().expand(fsi.size));
EXPECT_CALL(upload_mgr, store_resume)
.WillOnce([&fsi](const i_open_file &file) {
EXPECT_EQ(fsi.api_path, file.get_api_path());
EXPECT_EQ(fsi.source_path, file.get_source_path());
});
open_file file(test_chunk_size, 0U, fsi, provider, upload_mgr);
test_closeable_open_file(file, false, api_error::success, fsi.size,
source_path);
EXPECT_CALL(provider, set_item_meta(fsi.api_path, _))
.WillOnce([](const std::string &, const api_meta_map &meta) -> api_error {
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_CHANGED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_MODIFIED).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_SIZE).empty()));
EXPECT_NO_THROW(EXPECT_FALSE(meta.at(META_WRITTEN).empty()));
EXPECT_EQ(std::size_t(test_chunk_size * 3U),
utils::string::to_size_t(meta.at(META_SIZE)));
return api_error::success;
});
EXPECT_CALL(upload_mgr, remove_upload)
.WillOnce([&fsi](const std::string &api_path) {
EXPECT_EQ(fsi.api_path, api_path);
});
EXPECT_CALL(upload_mgr, queue_upload)
.WillOnce([&fsi](const i_open_file &cur_file) {
EXPECT_EQ(fsi.api_path, cur_file.get_api_path());
EXPECT_EQ(fsi.source_path, cur_file.get_source_path());
});
EXPECT_EQ(api_error::success, file.resize(test_chunk_size * 3U));
EXPECT_EQ(std::size_t(test_chunk_size * 3U), file.get_file_size());
EXPECT_FALSE(file.can_close());
EXPECT_TRUE(file.is_modified());
EXPECT_EQ(std::size_t(3U), file.get_read_state().size());
}
TEST_F(open_file_test, can_add_handle) {
event_system::instance().start();
console_consumer c;
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4u;
fsi.source_path = source_path;
event_consumer ec("filesystem_item_opened", [&fsi](const event &e) {
const auto &ee = dynamic_cast<const filesystem_item_opened &>(e);
EXPECT_STREQ(fsi.api_path.c_str(),
ee.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
ee.get_source().get<std::string>().c_str());
EXPECT_STREQ("0", ee.get_directory().get<std::string>().c_str());
});
event_consumer ec2("filesystem_item_handle_opened", [&fsi](const event &e) {
const auto &ee = dynamic_cast<const filesystem_item_handle_opened &>(e);
EXPECT_STREQ(fsi.api_path.c_str(),
ee.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
ee.get_source().get<std::string>().c_str());
EXPECT_STREQ("0", ee.get_directory().get<std::string>().c_str());
EXPECT_STREQ("1", ee.get_handle().get<std::string>().c_str());
});
EXPECT_CALL(provider, set_item_meta(fsi.api_path, META_SOURCE, _))
.WillOnce(Return(api_error::success));
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
event_capture capture(
{"filesystem_item_opened", "filesystem_item_handle_opened"});
open_file o(test_chunk_size, 0U, fsi, provider, upload_mgr);
#if defined(_WIN32)
o.add(1u, {});
EXPECT_EQ(nullptr, o.get_open_data(1u).directory_buffer);
#else
o.add(1u, O_RDWR | O_SYNC);
EXPECT_EQ(O_RDWR | O_SYNC, o.get_open_data(1u));
#endif
capture.wait_for_empty();
event_system::instance().stop();
}
TEST_F(open_file_test, can_remove_handle) {
event_system::instance().start();
console_consumer c;
const auto source_path =
test::generate_test_file_name("file_manager_open_file_test");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4u;
fsi.source_path = source_path;
event_consumer ec("filesystem_item_closed", [&fsi](const event &e) {
const auto &ee = dynamic_cast<const filesystem_item_closed &>(e);
EXPECT_STREQ(fsi.api_path.c_str(),
ee.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
ee.get_source().get<std::string>().c_str());
EXPECT_STREQ("0", ee.get_directory().get<std::string>().c_str());
});
event_consumer ec2("filesystem_item_handle_closed", [&fsi](const event &e) {
const auto &ee = dynamic_cast<const filesystem_item_handle_closed &>(e);
EXPECT_STREQ(fsi.api_path.c_str(),
ee.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
ee.get_source().get<std::string>().c_str());
EXPECT_STREQ("0", ee.get_directory().get<std::string>().c_str());
EXPECT_STREQ("1", ee.get_handle().get<std::string>().c_str());
});
EXPECT_CALL(upload_mgr, remove_resume)
.WillOnce(
[&fsi](const std::string &api_path, const std::string &source_path2) {
EXPECT_EQ(fsi.api_path, api_path);
EXPECT_EQ(fsi.source_path, source_path2);
});
EXPECT_CALL(provider, set_item_meta(fsi.api_path, META_SOURCE, _))
.WillOnce(Return(api_error::success));
event_capture capture({
"filesystem_item_opened",
"filesystem_item_handle_opened",
"filesystem_item_handle_closed",
"filesystem_item_closed",
});
open_file o(test_chunk_size, 0U, fsi, provider, upload_mgr);
#if defined(_WIN32)
o.add(1u, {});
#else
o.add(1u, O_RDWR | O_SYNC);
#endif
o.remove(1u);
capture.wait_for_empty();
event_system::instance().stop();
}
TEST_F(open_file_test,
can_read_locally_after_write_with_file_size_greater_than_existing_size) {
}
TEST_F(open_file_test, test_valid_download_chunks) {}
TEST_F(open_file_test, test_full_download_with_partial_chunk) {}
TEST_F(open_file_test, source_is_read_after_full_download) {}
} // namespace repertory

View File

@@ -0,0 +1,41 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "comm/packet/packet.hpp"
namespace repertory {
TEST(packet, encrypt_and_decrypt) {
packet test_packet;
test_packet.encode("test");
test_packet.encrypt("moose");
std::uint32_t size{};
EXPECT_EQ(0, test_packet.decode(size));
EXPECT_EQ(0, test_packet.decrypt("moose"));
std::string data;
EXPECT_EQ(0, test_packet.decode(data));
EXPECT_STREQ("test", data.c_str());
}
} // namespace repertory

View File

@@ -0,0 +1,751 @@
/*
Copyright <2018-2024> <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 0
#include "test_common.hpp"
#include "comm/curl/curl_comm.hpp"
#include "events/event_system.hpp"
#include "file_manager/file_manager.hpp"
#include "platform/platform.hpp"
#include "providers/encrypt/encrypt_provider.hpp"
#include "providers/i_provider.hpp"
#include "providers/s3/s3_provider.hpp"
#include "providers/sia/sia_provider.hpp"
#include "utils/collection.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
#include "utils/string.hpp"
#include "utils/time.hpp"
#include "utils/utils.hpp"
namespace {
#if defined(_WIN32)
using gid_t = std::uint32_t;
using uid_t = std::uint32_t;
static constexpr auto getgid() -> gid_t { return 0U; }
static constexpr auto getuid() -> uid_t { return 0U; }
#endif // defined(_WIN32)
const auto check_forced_dirs = [](const repertory::directory_item_list &list) {
static auto forced_dirs = std::array<std::string, 2>{".", ".."};
for (std::size_t i = 0U; i < forced_dirs.size(); ++i) {
const auto &item = list.at(i);
EXPECT_TRUE(item.directory);
EXPECT_STREQ(forced_dirs.at(i).c_str(), item.api_path.c_str());
EXPECT_STREQ("", item.api_parent.c_str());
EXPECT_EQ(std::size_t(0U), item.size);
}
};
const auto create_directory = [](repertory::i_provider &provider,
const std::string &api_path) {
auto date = repertory::utils::time::get_time_now();
auto meta = repertory::create_meta_attributes(
date, 1U, date + 1U, date + 2U, true, getgid(), "", 0700, date + 3U, 2U,
3U, 0U, api_path + "_src", getuid(), date + 4U);
EXPECT_EQ(repertory::api_error::success,
provider.create_directory(api_path, meta));
bool exists{};
EXPECT_EQ(repertory::api_error::success,
provider.is_directory(api_path, exists));
EXPECT_TRUE(exists);
repertory::api_meta_map meta2{};
EXPECT_EQ(repertory::api_error::success,
provider.get_item_meta(api_path, meta2));
EXPECT_EQ(date, repertory::utils::string::to_uint64(
meta2[repertory::META_ACCESSED]));
EXPECT_EQ(1U, repertory::utils::string::to_uint64(
meta2[repertory::META_ATTRIBUTES]));
EXPECT_EQ(date + 1U, repertory::utils::string::to_uint64(
meta2[repertory::META_CHANGED]));
EXPECT_EQ(date + 2U, repertory::utils::string::to_uint64(
meta2[repertory::META_CREATION]));
EXPECT_TRUE(
repertory::utils::string::to_bool(meta2.at(repertory::META_DIRECTORY)));
EXPECT_EQ(getgid(), static_cast<gid_t>(repertory::utils::string::to_uint32(
meta2[repertory::META_GID])));
EXPECT_EQ(std::uint32_t(0700),
repertory::utils::string::to_uint32(meta2[repertory::META_MODE]));
EXPECT_EQ(date + 3U, repertory::utils::string::to_uint64(
meta2[repertory::META_MODIFIED]));
EXPECT_EQ(2U,
repertory::utils::string::to_uint64(meta2[repertory::META_BACKUP]));
EXPECT_EQ(
3U, repertory::utils::string::to_uint64(meta2[repertory::META_OSXFLAGS]));
EXPECT_FALSE(
repertory::utils::string::to_bool(meta2[repertory::META_PINNED]));
EXPECT_EQ(std::uint64_t(0U),
repertory::utils::string::to_uint64(meta2[repertory::META_SIZE]));
EXPECT_STREQ((api_path + "_src").c_str(),
meta2[repertory::META_SOURCE].c_str());
EXPECT_EQ(getuid(), static_cast<uid_t>(repertory::utils::string::to_uint32(
meta2[repertory::META_UID])));
EXPECT_EQ(date + 4U, repertory::utils::string::to_uint64(
meta2[repertory::META_WRITTEN]));
};
const auto create_file = [](repertory::i_provider &provider,
const std::string &api_path) {
auto source_path = repertory::test::generate_test_file_name("providers_test");
auto date = repertory::utils::time::get_time_now();
auto meta = repertory::create_meta_attributes(
date, 1U, date + 1U, date + 2U, false, getgid(), "", 0700, date + 3U, 2U,
3U, 0U, source_path, getuid(), date + 4U);
EXPECT_EQ(repertory::api_error::success,
provider.create_file(api_path, meta));
bool exists{};
EXPECT_EQ(repertory::api_error::success, provider.is_file(api_path, exists));
EXPECT_TRUE(exists);
EXPECT_TRUE(repertory::utils::file::file{source_path}.remove());
repertory::api_meta_map meta2{};
EXPECT_EQ(repertory::api_error::success,
provider.get_item_meta(api_path, meta2));
EXPECT_EQ(date, repertory::utils::string::to_uint64(
meta2[repertory::META_ACCESSED]));
EXPECT_EQ(1U, repertory::utils::string::to_uint64(
meta2[repertory::META_ATTRIBUTES]));
EXPECT_EQ(date + 1U, repertory::utils::string::to_uint64(
meta2[repertory::META_CHANGED]));
EXPECT_EQ(date + 2U, repertory::utils::string::to_uint64(
meta2[repertory::META_CREATION]));
EXPECT_FALSE(
repertory::utils::string::to_bool(meta2.at(repertory::META_DIRECTORY)));
EXPECT_EQ(getgid(), static_cast<gid_t>(repertory::utils::string::to_uint32(
meta2[repertory::META_GID])));
EXPECT_EQ(std::uint32_t(0700),
repertory::utils::string::to_uint32(meta2[repertory::META_MODE]));
EXPECT_EQ(date + 3U, repertory::utils::string::to_uint64(
meta2[repertory::META_MODIFIED]));
EXPECT_EQ(2U,
repertory::utils::string::to_uint64(meta2[repertory::META_BACKUP]));
EXPECT_EQ(
3U, repertory::utils::string::to_uint64(meta2[repertory::META_OSXFLAGS]));
EXPECT_FALSE(
repertory::utils::string::to_bool(meta2[repertory::META_PINNED]));
EXPECT_EQ(std::uint64_t(0U),
repertory::utils::string::to_uint64(meta2[repertory::META_SIZE]));
EXPECT_STREQ(source_path.c_str(), meta2[repertory::META_SOURCE].c_str());
EXPECT_EQ(getuid(), static_cast<uid_t>(repertory::utils::string::to_uint32(
meta2[repertory::META_UID])));
EXPECT_EQ(date + 4U, repertory::utils::string::to_uint64(
meta2[repertory::META_WRITTEN]));
};
const auto decrypt_parts = [](const repertory::app_config &cfg,
std::string &path) {
if (path != "/" && path != "." && path != "..") {
auto parts = repertory::utils::string::split(path, '/', false);
for (auto &part : parts) {
if (part.empty()) {
continue;
}
EXPECT_EQ(true, repertory::utils::encryption::decrypt_file_name(
cfg.get_encrypt_config().encryption_token, part));
}
path = repertory::utils::string::join(parts, '/');
}
};
} // namespace
namespace repertory {
static void can_create_and_remove_directory(i_provider &provider) {
if (provider.is_read_only()) {
api_meta_map meta{};
EXPECT_EQ(api_error::not_implemented,
provider.create_directory("/moose", meta));
EXPECT_EQ(api_error::not_implemented, provider.remove_directory("/moose"));
return;
}
create_directory(provider, "/pt01");
EXPECT_EQ(api_error::success, provider.remove_directory("/pt01"));
bool exists{};
EXPECT_EQ(api_error::success, provider.is_directory("/pt01", exists));
EXPECT_FALSE(exists);
}
static void create_directory_fails_if_already_exists(i_provider &provider) {
if (provider.is_read_only()) {
return;
}
create_directory(provider, "/pt01");
api_meta_map meta{};
EXPECT_EQ(api_error::directory_exists,
provider.create_directory("/pt01", meta));
EXPECT_EQ(api_error::success, provider.remove_directory("/pt01"));
}
static void
create_directory_fails_if_file_already_exists(i_provider &provider) {
if (provider.is_read_only()) {
return;
}
create_file(provider, "/pt01");
api_meta_map meta{};
EXPECT_EQ(api_error::item_exists, provider.create_directory("/pt01", meta));
EXPECT_EQ(api_error::success, provider.remove_file("/pt01"));
}
static void create_directory_clone_source_meta(i_provider &provider) {
if (provider.is_read_only()) {
EXPECT_EQ(api_error::not_implemented,
provider.create_directory_clone_source_meta("/moose", "/moose"));
return;
}
create_directory(provider, "/clone");
api_meta_map meta_orig{};
EXPECT_EQ(api_error::success, provider.get_item_meta("/clone", meta_orig));
EXPECT_EQ(api_error::success,
provider.create_directory_clone_source_meta("/clone", "/clone2"));
api_meta_map meta_clone{};
EXPECT_EQ(api_error::success, provider.get_item_meta("/clone2", meta_clone));
EXPECT_EQ(meta_orig.size(), meta_clone.size());
for (const auto &item : meta_orig) {
if (item.first == META_KEY) {
if (item.second.empty() && meta_clone[item.first].empty()) {
continue;
}
EXPECT_STRNE(item.second.c_str(), meta_clone[item.first].c_str());
continue;
}
EXPECT_STREQ(item.second.c_str(), meta_clone[item.first].c_str());
}
EXPECT_EQ(api_error::success, provider.remove_directory("/clone"));
EXPECT_EQ(api_error::success, provider.remove_directory("/clone2"));
}
static void create_directory_clone_source_meta_fails_if_already_exists(
i_provider &provider) {
if (provider.is_read_only()) {
return;
}
create_directory(provider, "/clone");
create_directory(provider, "/clone2");
EXPECT_EQ(api_error::directory_exists,
provider.create_directory_clone_source_meta("/clone", "/clone2"));
EXPECT_EQ(api_error::success, provider.remove_directory("/clone"));
EXPECT_EQ(api_error::success, provider.remove_directory("/clone2"));
}
static void create_directory_clone_source_meta_fails_if_directory_not_found(
i_provider &provider) {
if (provider.is_read_only()) {
return;
}
EXPECT_EQ(api_error::directory_not_found,
provider.create_directory_clone_source_meta("/clone", "/clone2"));
}
static void create_directory_clone_source_meta_fails_if_file_already_exists(
i_provider &provider) {
if (provider.is_read_only()) {
return;
}
create_directory(provider, "/clone");
create_file(provider, "/clone2");
EXPECT_EQ(api_error::item_exists,
provider.create_directory_clone_source_meta("/clone", "/clone2"));
EXPECT_EQ(api_error::success, provider.remove_directory("/clone"));
EXPECT_EQ(api_error::success, provider.remove_file("/clone2"));
}
static void can_create_and_remove_file(i_provider &provider) {
if (provider.is_read_only()) {
api_meta_map meta{};
EXPECT_EQ(api_error::not_implemented,
provider.create_file("/moose.txt", meta));
return;
}
create_file(provider, "/pt01.txt");
bool exists{};
EXPECT_EQ(api_error::success, provider.is_file("/pt01.txt", exists));
EXPECT_TRUE(exists);
EXPECT_EQ(api_error::success, provider.remove_file("/pt01.txt"));
EXPECT_EQ(api_error::success, provider.is_file("/pt01.txt", exists));
EXPECT_FALSE(exists);
}
static void create_file_fails_if_already_exists(i_provider &provider) {
if (provider.is_read_only()) {
return;
}
create_file(provider, "/pt01.txt");
api_meta_map meta{};
EXPECT_EQ(api_error::item_exists, provider.create_file("/pt01.txt", meta));
EXPECT_EQ(api_error::success, provider.remove_file("/pt01.txt"));
}
static void
create_file_fails_if_directory_already_exists(i_provider &provider) {
if (provider.is_read_only()) {
return;
}
create_directory(provider, "/pt01");
api_meta_map meta{};
EXPECT_EQ(api_error::directory_exists, provider.create_file("/pt01", meta));
EXPECT_EQ(api_error::success, provider.remove_directory("/pt01"));
}
static void get_api_path_from_source(const app_config &cfg,
i_provider &provider) {
if (provider.get_provider_type() == provider_type::encrypt) {
const auto source_path =
utils::path::combine("./test_date/encrypt", {"test.txt"});
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(source_path, api_path));
std::string file_name{api_path.substr(1U)};
decrypt_parts(cfg, file_name);
EXPECT_STREQ("test.txt", file_name.c_str());
return;
}
create_file(provider, "/pt01.txt");
filesystem_item fsi{};
EXPECT_EQ(api_error::success,
provider.get_filesystem_item("/pt01.txt", false, fsi));
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(fsi.source_path, api_path));
EXPECT_STREQ("/pt01.txt", api_path.c_str());
EXPECT_EQ(api_error::success, provider.remove_file("/pt01.txt"));
}
static void
get_api_path_from_source_fails_if_file_not_found(const app_config &cfg,
i_provider &provider) {
std::string source_path{};
if (provider.get_provider_type() == provider_type::encrypt) {
source_path = utils::path::combine(cfg.get_encrypt_config().path,
{"test_not_found.txt"});
} else {
source_path = utils::path::combine("./", {"test_not_found.txt"});
}
std::string api_path{};
EXPECT_EQ(api_error::item_not_found,
provider.get_api_path_from_source(source_path, api_path));
EXPECT_TRUE(api_path.empty());
}
static void get_directory_item_count(const app_config &cfg,
i_provider &provider) {
if (provider.get_provider_type() == provider_type::encrypt) {
EXPECT_EQ(std::size_t(2U), provider.get_directory_item_count("/"));
EXPECT_EQ(std::size_t(0U), provider.get_directory_item_count("/not_found"));
const auto source_path =
utils::path::combine(test::get_test_input_dir(), {"encrypt", "sub10"});
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(source_path, api_path));
EXPECT_EQ(std::size_t(1U), provider.get_directory_item_count(api_path));
}
}
static void get_directory_items(const app_config &cfg, i_provider &provider) {
directory_item_list list{};
EXPECT_EQ(api_error::success, provider.get_directory_items("/", list));
check_forced_dirs(list);
if (provider.get_provider_type() == provider_type::encrypt) {
EXPECT_EQ(std::size_t(4U), list.size());
directory_item_list list_decrypted{list.begin() + 2U, list.end()};
for (auto &dir_item : list_decrypted) {
decrypt_parts(cfg, dir_item.api_parent);
decrypt_parts(cfg, dir_item.api_path);
}
auto dir = std::find_if(list_decrypted.begin(), list_decrypted.end(),
[](const directory_item &dir_item) -> bool {
return dir_item.directory;
});
EXPECT_LT(dir, list_decrypted.end());
EXPECT_STREQ("/sub10", dir->api_path.c_str());
EXPECT_STREQ("/", dir->api_parent.c_str());
EXPECT_EQ(std::size_t(0U), dir->size);
auto file = std::find_if(list_decrypted.begin(), list_decrypted.end(),
[](const directory_item &dir_item) -> bool {
return not dir_item.directory;
});
EXPECT_LT(file, list_decrypted.end());
EXPECT_STREQ("/test.txt", file->api_path.c_str());
EXPECT_STREQ("/", file->api_parent.c_str());
#if defined(_WIN32)
EXPECT_EQ(std::size_t(47U), file->size);
#else
EXPECT_EQ(std::size_t(46U), file->size);
#endif
const auto source_path =
utils::path::combine(cfg.get_encrypt_config().path, {"sub10"});
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(source_path, api_path));
list.clear();
EXPECT_EQ(api_error::success, provider.get_directory_items(api_path, list));
check_forced_dirs(list);
EXPECT_EQ(std::size_t(3U), list.size());
directory_item_list list_decrypted2{list.begin() + 2U, list.end()};
for (auto &dir_item : list_decrypted2) {
decrypt_parts(cfg, dir_item.api_parent);
decrypt_parts(cfg, dir_item.api_path);
}
auto file2 = std::find_if(list_decrypted2.begin(), list_decrypted2.end(),
[](const directory_item &dir_item) -> bool {
return not dir_item.directory;
});
EXPECT_LT(file2, list_decrypted2.end());
EXPECT_STREQ("/sub10/moose.txt", file2->api_path.c_str());
EXPECT_STREQ("/sub10", file2->api_parent.c_str());
#if defined(_WIN32)
EXPECT_EQ(std::size_t(46U), file2->size);
#else
EXPECT_EQ(std::size_t(45U), file2->size);
#endif
}
}
static void
get_directory_items_fails_if_directory_not_found(i_provider &provider) {
directory_item_list list{};
EXPECT_EQ(api_error::directory_not_found,
provider.get_directory_items("/not_found", list));
EXPECT_TRUE(list.empty());
}
static void get_directory_items_fails_if_item_is_file(const app_config &cfg,
i_provider &provider) {
if (provider.get_provider_type() == provider_type::encrypt) {
const auto source_path =
utils::path::combine(cfg.get_encrypt_config().path, {"test.txt"});
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(source_path, api_path));
directory_item_list list{};
EXPECT_EQ(api_error::item_exists,
provider.get_directory_items(api_path, list));
EXPECT_TRUE(list.empty());
}
}
static void get_file(const app_config &cfg, i_provider &provider) {
if (provider.get_provider_type() == provider_type::encrypt) {
const auto source_path =
utils::path::combine(cfg.get_encrypt_config().path, {"test.txt"});
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(source_path, api_path));
api_file file{};
EXPECT_EQ(api_error::success, provider.get_file(api_path, file));
decrypt_parts(cfg, file.api_path);
decrypt_parts(cfg, file.api_parent);
EXPECT_STREQ("/test.txt", file.api_path.c_str());
EXPECT_STREQ("/", file.api_parent.c_str());
#if defined(_WIN32)
EXPECT_EQ(std::size_t(47U), file.file_size);
#else
EXPECT_EQ(std::size_t(46U), file.file_size);
#endif
EXPECT_STREQ(source_path.c_str(), file.source_path.c_str());
}
}
static void get_file_fails_if_file_not_found(i_provider &provider) {
api_file file{};
EXPECT_EQ(api_error::item_not_found, provider.get_file("/not_found", file));
}
static void get_file_fails_if_item_is_directory(const app_config &cfg,
i_provider &provider) {
if (provider.get_provider_type() == provider_type::encrypt) {
const auto source_path =
utils::path::combine(cfg.get_encrypt_config().path, {"sub10"});
std::string api_path{};
EXPECT_EQ(api_error::success,
provider.get_api_path_from_source(source_path, api_path));
api_file file{};
EXPECT_EQ(api_error::directory_exists, provider.get_file(api_path, file));
}
}
static void get_file_list(const app_config &cfg, i_provider &provider) {
api_file_list list{};
std::string marker;
EXPECT_EQ(api_error::success, provider.get_file_list(list, marker));
if (provider.get_provider_type() == provider_type::encrypt) {
EXPECT_EQ(std::size_t(2U), list.size());
std::vector<std::string> expected_parents{
{"/"},
{"/sub10"},
};
std::vector<std::string> expected_paths{
{"/test.txt"},
{"/sub10/moose.txt"},
};
for (auto &file : list) {
decrypt_parts(cfg, file.api_parent);
decrypt_parts(cfg, file.api_path);
utils::collection::remove_element(expected_parents, file.api_parent);
utils::collection::remove_element(expected_paths, file.api_path);
}
EXPECT_TRUE(expected_parents.empty());
EXPECT_TRUE(expected_paths.empty());
}
}
static void run_tests(const app_config &cfg, i_provider &provider) {
get_file_list(cfg, provider);
ASSERT_FALSE(::testing::Test::HasFailure());
can_create_and_remove_directory(provider);
can_create_and_remove_file(provider);
create_directory_fails_if_already_exists(provider);
create_directory_fails_if_file_already_exists(provider);
create_directory_clone_source_meta(provider);
create_directory_clone_source_meta_fails_if_already_exists(provider);
create_directory_clone_source_meta_fails_if_directory_not_found(provider);
create_directory_clone_source_meta_fails_if_file_already_exists(provider);
create_file_fails_if_already_exists(provider);
create_file_fails_if_directory_already_exists(provider);
get_api_path_from_source(cfg, provider);
get_api_path_from_source_fails_if_file_not_found(cfg, provider);
// TODO: continue here
get_directory_items(cfg, provider);
get_directory_items_fails_if_directory_not_found(provider);
get_directory_items_fails_if_item_is_file(cfg, provider);
get_directory_item_count(cfg, provider);
get_file(cfg, provider);
get_file_fails_if_file_not_found(provider);
get_file_fails_if_item_is_directory(cfg, provider);
// TODO need to test read when file size changes for encrypt provider
/* get_file_list(provider);
get_file_size(provider);
get_filesystem_item(provider);
get_filesystem_item_and_file(provider);
get_filesystem_item_from_source_path(provider);
get_item_meta(provider);
get_item_meta2(provider);
get_pinned_files(provider);
get_total_drive_space(provider);
get_total_item_count(provider);
get_used_drive_space(provider);
is_directory(provider);
is_file(provider);
is_file_writeable(provider);
read_file_bytes(provider);
remove_directory(provider);
remove_file(provider);
remove_item_meta(provider);
rename_file(provider);
set_item_meta(provider);
set_item_meta2(provider);
upload_file(provider); */
}
TEST(providers, encrypt_provider) {
const auto config_path =
utils::path::combine(test::get_test_output_dir(), {"encrypt_provider"});
console_consumer consumer{};
event_system::instance().start();
{
app_config cfg(provider_type::encrypt, config_path);
const auto encrypt_path =
utils::path::combine(test::get_test_input_dir(), {"encrypt"});
EXPECT_STREQ(
encrypt_path.c_str(),
cfg.set_value_by_name("EncryptConfig.Path", encrypt_path).c_str());
EXPECT_STREQ(
"test_token",
cfg.set_value_by_name("EncryptConfig.EncryptionToken", "test_token")
.c_str());
encrypt_provider provider{cfg};
file_manager mgr(cfg, provider);
mgr.start();
EXPECT_TRUE(provider.start(
[&provider](bool directory, api_file &file) -> api_error {
return provider_meta_handler(provider, directory, file);
},
&mgr));
EXPECT_EQ(provider_type::encrypt, provider.get_provider_type());
EXPECT_TRUE(provider.is_read_only());
EXPECT_TRUE(provider.is_online());
EXPECT_FALSE(provider.is_rename_supported());
run_tests(cfg, provider);
provider.stop();
mgr.stop();
}
event_system::instance().stop();
}
TEST(providers, s3_provider) {
const auto config_path =
utils::path::combine(test::get_test_output_dir(), {"s3_provider"});
console_consumer consumer{};
event_system::instance().start();
{
app_config cfg(provider_type::s3, config_path);
{
app_config src_cfg(
provider_type::s3,
utils::path::combine(test::get_test_config_dir(), {"storj"}));
cfg.set_s3_config(src_cfg.get_s3_config());
}
curl_comm comm{cfg.get_s3_config()};
s3_provider provider{cfg, comm};
file_manager mgr(cfg, provider);
mgr.start();
EXPECT_TRUE(provider.start(
[&provider](bool directory, api_file &file) -> api_error {
return provider_meta_handler(provider, directory, file);
},
&mgr));
EXPECT_EQ(provider_type::s3, provider.get_provider_type());
EXPECT_FALSE(provider.is_read_only());
EXPECT_TRUE(provider.is_online());
EXPECT_FALSE(provider.is_rename_supported());
run_tests(cfg, provider);
provider.stop();
mgr.stop();
}
event_system::instance().stop();
}
TEST(providers, sia_provider) {
const auto config_path =
utils::path::combine(test::get_test_output_dir(), {"sia_provider"});
console_consumer consumer{};
event_system::instance().start();
{
app_config cfg(provider_type::sia, config_path);
{
app_config src_cfg(
provider_type::sia,
utils::path::combine(test::get_test_config_dir(), {"sia"}));
cfg.set_host_config(src_cfg.get_host_config());
}
curl_comm comm{cfg.get_host_config()};
sia_provider provider{cfg, comm};
file_manager mgr(cfg, provider);
mgr.start();
EXPECT_TRUE(provider.start(
[&provider](bool directory, api_file &file) -> api_error {
return provider_meta_handler(provider, directory, file);
},
&mgr));
EXPECT_EQ(provider_type::sia, provider.get_provider_type());
EXPECT_FALSE(provider.is_read_only());
EXPECT_TRUE(provider.is_online());
EXPECT_TRUE(provider.is_rename_supported());
run_tests(cfg, provider);
provider.stop();
mgr.stop();
}
event_system::instance().stop();
}
} // namespace repertory
#endif // 0

View File

@@ -0,0 +1,566 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "file_manager/ring_buffer_open_file.hpp"
#include "mocks/mock_provider.hpp"
#include "mocks/mock_upload_manager.hpp"
#include "platform/platform.hpp"
#include "utils/file_utils.hpp"
#include "utils/path.hpp"
namespace {
constexpr const std::size_t test_chunk_size{1024U};
const auto ring_buffer_dir{
repertory::utils::path::combine(
repertory::test::get_test_output_dir(),
{"file_manager_ring_buffer_open_file_test"}),
};
} // namespace
namespace repertory {
class ring_buffer_open_file_test : public ::testing::Test {
public:
console_consumer con_consumer;
mock_provider provider;
protected:
void SetUp() override { event_system::instance().start(); }
void TearDown() override { event_system::instance().stop(); }
};
TEST_F(ring_buffer_open_file_test, can_forward_to_last_chunk) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 16U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(0U, 3U);
file.forward(4U);
EXPECT_EQ(std::size_t(7U), file.get_current_chunk());
EXPECT_EQ(std::size_t(0U), file.get_first_chunk());
EXPECT_EQ(std::size_t(7U), file.get_last_chunk());
for (std::size_t chunk = 0U; chunk < 8U; chunk++) {
EXPECT_TRUE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test,
can_forward_to_last_chunk_if_count_is_greater_than_remaining) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 16U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(0U, 3U);
file.forward(100U);
EXPECT_EQ(std::size_t(15U), file.get_current_chunk());
EXPECT_EQ(std::size_t(8U), file.get_first_chunk());
EXPECT_EQ(std::size_t(15U), file.get_last_chunk());
for (std::size_t chunk = 8U; chunk <= 15U; chunk++) {
EXPECT_FALSE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test, can_forward_after_last_chunk) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 16U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(0U, 3U);
file.forward(5U);
EXPECT_EQ(std::size_t(8U), file.get_current_chunk());
EXPECT_EQ(std::size_t(1U), file.get_first_chunk());
EXPECT_EQ(std::size_t(8U), file.get_last_chunk());
EXPECT_FALSE(file.get_read_state(8U));
for (std::size_t chunk = 1U; chunk < 8U; chunk++) {
EXPECT_TRUE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test, can_forward_and_rollover_after_last_chunk) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(16U, 20U);
file.forward(8U);
EXPECT_EQ(std::size_t(28U), file.get_current_chunk());
EXPECT_EQ(std::size_t(21U), file.get_first_chunk());
EXPECT_EQ(std::size_t(28U), file.get_last_chunk());
}
}
TEST_F(ring_buffer_open_file_test, can_reverse_to_first_chunk) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 16U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(0U, 3U);
file.reverse(3U);
EXPECT_EQ(std::size_t(0U), file.get_current_chunk());
EXPECT_EQ(std::size_t(0U), file.get_first_chunk());
EXPECT_EQ(std::size_t(7U), file.get_last_chunk());
for (std::size_t chunk = 0U; chunk < 8U; chunk++) {
EXPECT_TRUE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test,
can_reverse_to_first_chunk_if_count_is_greater_than_remaining) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 16U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(0U, 3U);
file.reverse(13U);
EXPECT_EQ(std::size_t(0U), file.get_current_chunk());
EXPECT_EQ(std::size_t(0U), file.get_first_chunk());
EXPECT_EQ(std::size_t(7U), file.get_last_chunk());
for (std::size_t chunk = 0U; chunk < 8U; chunk++) {
EXPECT_TRUE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test, can_reverse_before_first_chunk) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 16U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(1U, 3U);
file.reverse(3U);
EXPECT_EQ(std::size_t(0U), file.get_current_chunk());
EXPECT_EQ(std::size_t(0U), file.get_first_chunk());
EXPECT_EQ(std::size_t(7U), file.get_last_chunk());
EXPECT_FALSE(file.get_read_state(0U));
for (std::size_t chunk = 1U; chunk < 8U; chunk++) {
EXPECT_TRUE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test,
can_reverse_and_rollover_before_first_chunk) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(16U, 20U);
file.reverse(8U);
EXPECT_EQ(std::size_t(12U), file.get_current_chunk());
EXPECT_EQ(std::size_t(12U), file.get_first_chunk());
EXPECT_EQ(std::size_t(19U), file.get_last_chunk());
EXPECT_FALSE(file.get_read_state(12U));
EXPECT_FALSE(file.get_read_state(13U));
EXPECT_FALSE(file.get_read_state(14U));
EXPECT_FALSE(file.get_read_state(15U));
for (std::size_t chunk = 16U; chunk <= file.get_last_chunk(); chunk++) {
EXPECT_TRUE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test, can_reverse_full_ring) {
auto source_path = test::generate_test_file_name("ring_buffer_open_file");
EXPECT_CALL(provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32U;
fsi.source_path = source_path;
{
ring_buffer_open_file file(ring_buffer_dir, test_chunk_size, 30U, fsi,
provider, 8U);
file.set(8U, 15U);
file.reverse(16U);
EXPECT_EQ(std::size_t(0U), file.get_current_chunk());
EXPECT_EQ(std::size_t(0U), file.get_first_chunk());
EXPECT_EQ(std::size_t(7U), file.get_last_chunk());
for (std::size_t chunk = 0U; chunk <= file.get_last_chunk(); chunk++) {
EXPECT_FALSE(file.get_read_state(chunk));
}
}
}
TEST_F(ring_buffer_open_file_test, read_full_file) {
auto &nf = test::create_random_file(test_chunk_size * 33u + 11u);
auto download_source_path = nf.get_path();
auto dest_path = test::generate_test_file_name("ring_buffer_open_file");
mock_provider mp;
EXPECT_CALL(mp, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 33u + 11u;
fsi.source_path = test::generate_test_file_name("ring_buffer_open_file");
std::mutex read_mtx;
EXPECT_CALL(mp, read_file_bytes)
.WillRepeatedly([&read_mtx, &nf](const std::string & /* api_path */,
std::size_t size, std::uint64_t offset,
data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = nf.read(data, offset, &bytes_read) ? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
ring_buffer_open_file rb(ring_buffer_dir, test_chunk_size, 30U, fsi, mp,
8u);
auto ptr = utils::file::file::open_or_create_file(dest_path);
auto &nf2 = *ptr;
EXPECT_TRUE(nf2);
auto to_read = fsi.size;
std::size_t chunk = 0u;
while (to_read) {
data_buffer data{};
EXPECT_EQ(api_error::success,
rb.read(test_chunk_size, chunk * test_chunk_size, data));
std::size_t bytes_written{};
EXPECT_TRUE(nf2.write(data, chunk * test_chunk_size, &bytes_written));
chunk++;
to_read -= data.size();
}
nf2.close();
nf.close();
auto hash1 = utils::file::file(download_source_path).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
TEST_F(ring_buffer_open_file_test, read_full_file_in_reverse) {
auto &nf = test::create_random_file(test_chunk_size * 32u);
auto download_source_path = nf.get_path();
auto dest_path = test::generate_test_file_name("ring_buffer_open_file");
mock_provider mp;
EXPECT_CALL(mp, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32u;
fsi.source_path = test::generate_test_file_name("ring_buffer_open_file");
std::mutex read_mtx;
EXPECT_CALL(mp, read_file_bytes)
.WillRepeatedly([&read_mtx, &nf](const std::string & /* api_path */,
std::size_t size, std::uint64_t offset,
data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = nf.read(data, offset, &bytes_read) ? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
ring_buffer_open_file rb(ring_buffer_dir, test_chunk_size, 30U, fsi, mp,
8u);
auto ptr = utils::file::file::open_or_create_file(dest_path);
auto &nf2 = *ptr;
EXPECT_TRUE(nf2);
auto to_read = fsi.size;
std::size_t chunk = rb.get_total_chunks() - 1U;
while (to_read > 0U) {
data_buffer data{};
EXPECT_EQ(api_error::success,
rb.read(test_chunk_size, chunk * test_chunk_size, data));
std::size_t bytes_written{};
EXPECT_TRUE(nf2.write(data, chunk * test_chunk_size, &bytes_written));
--chunk;
to_read -= data.size();
}
nf2.close();
nf.close();
auto hash1 = utils::file::file(download_source_path).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
TEST_F(ring_buffer_open_file_test, read_full_file_in_partial_chunks) {
auto &nf = test::create_random_file(test_chunk_size * 32u);
auto download_source_path = nf.get_path();
auto dest_path = test::generate_test_file_name("test");
mock_provider mp;
EXPECT_CALL(mp, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32u;
fsi.source_path = test::generate_test_file_name("test");
std::mutex read_mtx;
EXPECT_CALL(mp, read_file_bytes)
.WillRepeatedly([&read_mtx, &nf](const std::string & /* api_path */,
std::size_t size, std::uint64_t offset,
data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = nf.read(data, offset, &bytes_read) ? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
ring_buffer_open_file rb(ring_buffer_dir, test_chunk_size, 30U, fsi, mp,
8u);
auto ptr = utils::file::file::open_or_create_file(dest_path);
auto &nf2 = *ptr;
EXPECT_TRUE(nf2);
// EXPECT_EQ(api_error::success, native_file::create_or_open(dest_path,
// nf2));
auto total_read = std::uint64_t(0u);
while (total_read < fsi.size) {
data_buffer data{};
EXPECT_EQ(api_error::success, rb.read(3u, total_read, data));
std::size_t bytes_written{};
EXPECT_TRUE(
nf2.write(data.data(), data.size(), total_read, &bytes_written));
total_read += data.size();
}
nf2.close();
nf.close();
auto hash1 = utils::file::file(download_source_path).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
TEST_F(ring_buffer_open_file_test,
read_full_file_in_partial_chunks_in_reverse) {
auto &nf = test::create_random_file(test_chunk_size * 32u);
auto download_source_path = nf.get_path();
auto dest_path = test::generate_test_file_name("ring_buffer_open_file");
mock_provider mp;
EXPECT_CALL(mp, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.directory = false;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 32u;
fsi.source_path = test::generate_test_file_name("ring_buffer_open_file");
std::mutex read_mtx;
EXPECT_CALL(mp, read_file_bytes)
.WillRepeatedly([&read_mtx, &nf](const std::string & /* api_path */,
std::size_t size, std::uint64_t offset,
data_buffer &data,
stop_type &stop_requested) -> api_error {
mutex_lock lock(read_mtx);
EXPECT_FALSE(stop_requested);
std::size_t bytes_read{};
data.resize(size);
auto ret = nf.read(data, offset, &bytes_read) ? api_error::success
: api_error::os_error;
EXPECT_EQ(bytes_read, data.size());
return ret;
});
{
ring_buffer_open_file rb(ring_buffer_dir, test_chunk_size, 30U, fsi, mp,
8u);
auto ptr = utils::file::file::open_or_create_file(dest_path);
auto &nf2 = *ptr;
EXPECT_TRUE(nf2);
std::uint64_t total_read{0U};
auto read_size{3U};
while (total_read < fsi.size) {
auto offset = fsi.size - total_read - read_size;
auto remain = fsi.size - total_read;
data_buffer data{};
EXPECT_EQ(api_error::success,
rb.read(static_cast<std::size_t>(
std::min(remain, std::uint64_t(read_size))),
(remain >= read_size) ? offset : 0u, data));
std::size_t bytes_written{};
EXPECT_TRUE(
nf2.write(data, (remain >= read_size) ? offset : 0u, &bytes_written));
total_read += data.size();
}
nf2.close();
nf.close();
auto hash1 = utils::file::file(download_source_path).sha256();
auto hash2 = utils::file::file(dest_path).sha256();
EXPECT_TRUE(hash1.has_value());
EXPECT_TRUE(hash2.has_value());
if (hash1.has_value() && hash2.has_value()) {
EXPECT_STREQ(hash1.value().c_str(), hash2.value().c_str());
}
}
}
} // namespace repertory

View File

@@ -0,0 +1,184 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "file_manager/upload.hpp"
#include "mocks/mock_provider.hpp"
#include "utils/event_capture.hpp"
namespace repertory {
static constexpr const std::size_t test_chunk_size{1024U};
TEST(upload, can_upload_a_valid_file) {
console_consumer con;
event_system::instance().start();
const auto source_path = test::generate_test_file_name("upload_test");
mock_provider mock_prov;
EXPECT_CALL(mock_prov, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4U;
fsi.source_path = source_path;
event_consumer evt_com("file_upload_completed", [&fsi](const event &evt) {
const auto &comp_evt = dynamic_cast<const file_upload_completed &>(evt);
EXPECT_STREQ(fsi.api_path.c_str(),
comp_evt.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
comp_evt.get_source().get<std::string>().c_str());
EXPECT_STREQ("success", comp_evt.get_result().get<std::string>().c_str());
EXPECT_STREQ("0", comp_evt.get_cancelled().get<std::string>().c_str());
});
EXPECT_CALL(mock_prov, upload_file(fsi.api_path, fsi.source_path, _))
.WillOnce([](const std::string &, const std::string &,
stop_type &stop_requested) -> api_error {
EXPECT_FALSE(stop_requested);
return api_error::success;
});
upload upload(fsi, mock_prov);
event_capture evt_cap({"file_upload_completed"});
evt_cap.wait_for_empty();
EXPECT_EQ(api_error::success, upload.get_api_error());
EXPECT_FALSE(upload.is_cancelled());
event_system::instance().stop();
}
TEST(upload, can_cancel_upload) {
console_consumer con;
event_system::instance().start();
const auto source_path = test::generate_test_file_name("upload_test");
mock_provider mock_provider;
EXPECT_CALL(mock_provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4U;
fsi.source_path = source_path;
event_consumer evt_con("file_upload_completed", [&fsi](const event &evt) {
const auto &comp_evt = dynamic_cast<const file_upload_completed &>(evt);
EXPECT_STREQ(fsi.api_path.c_str(),
comp_evt.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
comp_evt.get_source().get<std::string>().c_str());
EXPECT_STREQ("comm_error",
comp_evt.get_result().get<std::string>().c_str());
EXPECT_STREQ("1", comp_evt.get_cancelled().get<std::string>().c_str());
});
std::mutex mtx;
std::condition_variable notify;
EXPECT_CALL(mock_provider, upload_file(fsi.api_path, fsi.source_path, _))
.WillOnce([&notify, &mtx](const std::string &, const std::string &,
stop_type &stop_requested) -> api_error {
EXPECT_FALSE(stop_requested);
unique_mutex_lock lock(mtx);
notify.notify_one();
lock.unlock();
lock.lock();
notify.wait(lock);
lock.unlock();
EXPECT_TRUE(stop_requested);
return api_error::comm_error;
});
unique_mutex_lock lock(mtx);
upload upload(fsi, mock_provider);
notify.wait(lock);
upload.cancel();
notify.notify_one();
lock.unlock();
event_capture evt_cap({"file_upload_completed"});
evt_cap.wait_for_empty();
EXPECT_EQ(api_error::comm_error, upload.get_api_error());
EXPECT_TRUE(upload.is_cancelled());
event_system::instance().stop();
}
TEST(upload, can_stop_upload) {
console_consumer con;
event_system::instance().start();
const auto source_path = test::generate_test_file_name("upload_test");
mock_provider mock_provider;
EXPECT_CALL(mock_provider, is_read_only()).WillRepeatedly(Return(false));
filesystem_item fsi;
fsi.api_path = "/test.txt";
fsi.size = test_chunk_size * 4U;
fsi.source_path = source_path;
event_consumer evt_con("file_upload_completed", [&fsi](const event &evt) {
const auto &evt_com = dynamic_cast<const file_upload_completed &>(evt);
EXPECT_STREQ(fsi.api_path.c_str(),
evt_com.get_api_path().get<std::string>().c_str());
EXPECT_STREQ(fsi.source_path.c_str(),
evt_com.get_source().get<std::string>().c_str());
EXPECT_STREQ("comm_error", evt_com.get_result().get<std::string>().c_str());
EXPECT_STREQ("0", evt_com.get_cancelled().get<std::string>().c_str());
});
EXPECT_CALL(mock_provider, upload_file(fsi.api_path, fsi.source_path, _))
.WillOnce([](const std::string &, const std::string &,
stop_type &stop_requested) -> api_error {
std::this_thread::sleep_for(3s);
EXPECT_TRUE(stop_requested);
return api_error::comm_error;
});
event_capture evt_cap({"file_upload_completed"});
{
upload upload(fsi, mock_provider);
}
evt_cap.wait_for_empty();
event_system::instance().stop();
}
} // namespace repertory

View File

@@ -0,0 +1,73 @@
/*
Copyright <2018-2024> <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.
*/
#include "test_common.hpp"
#include "providers/s3/s3_provider.hpp"
#include "utils/file.hpp"
namespace repertory {
TEST(utils, convert_api_date) {
#if defined(_WIN32)
auto file_time = utils::time::unix_time_to_filetime(
s3_provider::convert_api_date("2009-10-12T17:50:30.111Z"));
SYSTEMTIME st{};
FileTimeToSystemTime(&file_time, &st);
EXPECT_EQ(2009, st.wYear);
EXPECT_EQ(10, st.wMonth);
EXPECT_EQ(12, st.wDay);
EXPECT_EQ(17, st.wHour);
EXPECT_EQ(50, st.wMinute);
EXPECT_EQ(30, st.wSecond);
EXPECT_EQ(111, st.wMilliseconds);
#else // !defined(_WIN32)
auto unix_time = s3_provider::convert_api_date("2009-10-12T17:50:30.111Z") /
utils::time::NANOS_PER_SECOND;
auto *tm_data = gmtime(reinterpret_cast<time_t *>(&unix_time));
EXPECT_TRUE(tm_data != nullptr);
if (tm_data != nullptr) {
EXPECT_EQ(2009, tm_data->tm_year + 1900);
EXPECT_EQ(10, tm_data->tm_mon + 1);
EXPECT_EQ(12, tm_data->tm_mday);
EXPECT_EQ(17, tm_data->tm_hour);
EXPECT_EQ(50, tm_data->tm_min);
EXPECT_EQ(30, tm_data->tm_sec);
auto millis = (s3_provider::convert_api_date("2009-10-12T17:50:30.111Z") %
utils::time::NANOS_PER_SECOND) /
1000000UL;
EXPECT_EQ(111U, millis);
}
#endif // defined(_WIN32)
}
TEST(utils, generate_sha256) {
auto res = utils::file::file{__FILE__}.sha256();
EXPECT_TRUE(res.has_value());
if (res.has_value()) {
std::cout << res.value() << std::endl;
}
}
} // namespace repertory

View File

@@ -0,0 +1,221 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, cr8_attr_can_create_new_file_with_normal_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_1"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
auto attr = ::GetFileAttributesA(file_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE, attr);
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
TYPED_TEST(winfsp_test, cr8_attr_can_create_new_file_with_read_only_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_1"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_READONLY, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
auto attr = ::GetFileAttributesA(file_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY, attr);
EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(), FILE_ATTRIBUTE_NORMAL));
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
// TYPED_TEST(winfsp_test, cr8_attr_can_create_new_file_with_system_attribute) {
// auto file_path{
// utils::path::combine(this->mount_location, {"test_file_1"}),
// };
//
// auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ |
// GENERIC_WRITE,
// FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
// CREATE_NEW, FILE_ATTRIBUTE_SYSTEM, nullptr);
// ASSERT_NE(INVALID_HANDLE_VALUE, handle);
// ::CloseHandle(handle);
//
// auto attr = ::GetFileAttributesA(file_path.c_str());
// EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_SYSTEM, attr);
//
// EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(),
// FILE_ATTRIBUTE_NORMAL));
//
// EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
// }
TYPED_TEST(winfsp_test, cr8_attr_can_create_new_file_with_hidden_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_1"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_HIDDEN, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
auto attr = ::GetFileAttributesA(file_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN, attr);
EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(), FILE_ATTRIBUTE_NORMAL));
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
TYPED_TEST(winfsp_test, cr8_attr_can_create_always_file_with_normal_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_1"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
auto attr = ::GetFileAttributesA(file_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE, attr);
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
TYPED_TEST(winfsp_test, cr8_attr_can_create_file_with_read_only_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_1"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_READONLY, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
auto attr = ::GetFileAttributesA(file_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY, attr);
EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(), FILE_ATTRIBUTE_NORMAL));
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
// TYPED_TEST(winfsp_test,
// cr8_attr_can_create_always_file_with_system_attribute) {
// auto file_path{
// utils::path::combine(this->mount_location, {"test_file_1"}),
// };
//
// auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ |
// GENERIC_WRITE,
// FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
// CREATE_ALWAYS, FILE_ATTRIBUTE_SYSTEM, nullptr);
// ASSERT_NE(INVALID_HANDLE_VALUE, handle);
// ::CloseHandle(handle);
//
// auto attr = ::GetFileAttributesA(file_path.c_str());
// EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_SYSTEM, attr);
//
// EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(),
// FILE_ATTRIBUTE_NORMAL));
//
// EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
// }
TYPED_TEST(winfsp_test, cr8_attr_can_create_always_file_with_hidden_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_1"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
auto attr = ::GetFileAttributesA(file_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN, attr);
EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(), FILE_ATTRIBUTE_NORMAL));
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
TYPED_TEST(winfsp_test, cr8_attr_can_handle_read_only_directory) {
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_1"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_1"}),
};
EXPECT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
EXPECT_TRUE(::SetFileAttributesA(
dir_path.c_str(), FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY));
auto attr = ::GetFileAttributesA(dir_path.c_str());
EXPECT_EQ((FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY), attr);
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
EXPECT_FALSE(::RemoveDirectoryA(dir_path.c_str()));
EXPECT_EQ(ERROR_ACCESS_DENIED, ::GetLastError());
EXPECT_TRUE(::SetFileAttributesA(dir_path.c_str(), FILE_ATTRIBUTE_DIRECTORY));
attr = ::GetFileAttributesA(dir_path.c_str());
EXPECT_EQ(FILE_ATTRIBUTE_DIRECTORY, attr);
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,83 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, cr8_nl_can_create_file_of_max_component_length) {
if (this->current_provider == provider_type::s3) {
return;
}
DWORD max_length{};
EXPECT_TRUE(::GetVolumeInformationA(this->mount_location.c_str(), nullptr, 0,
nullptr, &max_length, nullptr, nullptr,
0));
EXPECT_EQ(255U, max_length);
auto file_path = utils::path::combine(this->mount_location,
{
std::string(max_length, 'a'),
});
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
EXPECT_TRUE(::CloseHandle(handle));
}
TYPED_TEST(winfsp_test,
cr8_nl_can_not_create_file_greater_than_max_component_length) {
if (this->current_provider == provider_type::s3) {
return;
}
DWORD max_length{};
EXPECT_TRUE(::GetVolumeInformationA(this->mount_location.c_str(), nullptr, 0,
nullptr, &max_length, nullptr, nullptr,
0));
EXPECT_EQ(255U, max_length);
auto file_path = utils::path::combine(this->mount_location,
{
std::string(max_length + 1U, 'a'),
});
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_INVALID_NAME, ::GetLastError());
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,191 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, cr8_file_can_create_file) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_0"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, cr8_file_create_new_fails_when_file_exists) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_0"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_EXISTS, ::GetLastError());
}
TYPED_TEST(winfsp_test, cr8_file_can_open_existing_file) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_0"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, cr8_file_create_always_succeeds_when_file_exists) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_0"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
// EXPECT file_size is 0
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, cr8_file_can_delete_file_after_close) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_0"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
// EXPECT file not found
}
TYPED_TEST(winfsp_test,
cr8_file_cannot_create_files_with_invalid_characters_in_path) {
for (auto &&invalid_char : std::array<std::string, 7U>{
{"*", ":", "<", ">", "?", "|", "\""},
}) {
auto handle = ::CreateFileA(
(this->mount_location + "\\" + invalid_char + "\\test_file_0").c_str(),
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_EQ(INVALID_HANDLE_VALUE, handle);
if (handle != INVALID_HANDLE_VALUE) {
std::cout << "char: " << invalid_char << std::endl;
}
EXPECT_EQ(ERROR_INVALID_NAME, ::GetLastError());
}
}
TYPED_TEST(winfsp_test,
cr8_file_cannot_create_stream_files_with_extra_component_in_path) {
auto file_path{
utils::path::combine(this->mount_location,
{
"test_file_0:test",
"moose",
}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_INVALID_NAME, ::GetLastError());
}
TYPED_TEST(winfsp_test, cr8_file_can_create_directory) {
auto dir_path{
utils::path::combine(this->mount_location,
{
"test_dir_0",
}),
};
EXPECT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
EXPECT_FALSE(::CreateDirectoryA(dir_path.c_str(), nullptr));
}
TYPED_TEST(winfsp_test, cr8_file_directory_delete_fails_if_not_empty) {
auto dir_path{
utils::path::combine(this->mount_location,
{
"test_dir_0",
}),
};
auto file_path{
utils::path::combine(dir_path,
{
"test_file_0",
}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
handle = ::CreateFileA(
dir_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
handle = ::CreateFileA(
dir_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
handle = ::CreateFileA(
dir_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,279 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, delete_directory_fails_if_directory_not_empty) {
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_3"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_3"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_FALSE(::RemoveDirectoryA(dir_path.c_str()));
EXPECT_EQ(ERROR_DIR_NOT_EMPTY, ::GetLastError());
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
EXPECT_FALSE(::DeleteFileA(file_path.c_str()));
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
EXPECT_FALSE(::RemoveDirectoryA(dir_path.c_str()));
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
TYPED_TEST(winfsp_test,
delete_read_file_attributes_fails_if_delete_is_pending) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_3"}),
};
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
handle = ::CreateFileA(file_path.c_str(), DELETE, FILE_SHARE_DELETE, nullptr,
OPEN_EXISTING, 0, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
typedef struct {
BOOLEAN Disposition;
} MY_FILE_DISPOSITION_INFO;
MY_FILE_DISPOSITION_INFO disp_info{TRUE};
EXPECT_TRUE(::SetFileInformationByHandle(handle, FileDispositionInfo,
&disp_info, sizeof disp_info));
auto handle2 = ::CreateFileA(file_path.c_str(), FILE_READ_ATTRIBUTES, 0,
nullptr, OPEN_EXISTING, 0, nullptr);
EXPECT_EQ(INVALID_HANDLE_VALUE, handle2);
EXPECT_EQ(ERROR_ACCESS_DENIED, ::GetLastError());
::CloseHandle(handle);
EXPECT_FALSE(::DeleteFileA(file_path.c_str()));
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
TYPED_TEST(winfsp_test, delete_can_handle_mmap_after_file_deletion) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_3"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
auto *mapping =
::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
sys_info.dwAllocationGranularity, nullptr);
EXPECT_TRUE(mapping != nullptr);
EXPECT_TRUE(::CloseHandle(handle));
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, nullptr);
EXPECT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
EXPECT_TRUE(::CloseHandle(mapping));
}
TYPED_TEST(winfsp_test, delete_can_delete_after_mapping) {
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_3"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_3"}),
};
auto file_path2{
utils::path::combine(dir_path, {"test_file2_3"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto seed = static_cast<unsigned>(std::time(nullptr));
srand(seed);
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
{
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto *mapping =
::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
16U * sys_info.dwAllocationGranularity, nullptr);
EXPECT_TRUE(::CloseHandle(handle));
ASSERT_TRUE(mapping != nullptr);
auto *view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ASSERT_TRUE(view != nullptr);
for (PUINT8 ptr = reinterpret_cast<PUINT8>(view),
end = ptr + 16U * sys_info.dwAllocationGranularity;
end > ptr; ++ptr) {
*ptr = rand() & 0xFF;
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
}
{
auto handle =
::CreateFileA(file_path2.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto mapping =
::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
16U * sys_info.dwAllocationGranularity, nullptr);
EXPECT_TRUE(::CloseHandle(handle));
ASSERT_TRUE(mapping != nullptr);
auto view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
EXPECT_TRUE(view != nullptr);
for (PUINT8 ptr = reinterpret_cast<PUINT8>(view),
end = ptr + 16U * sys_info.dwAllocationGranularity;
end > ptr; ++ptr) {
*ptr = rand() & 0xFF;
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
}
ASSERT_TRUE(::DeleteFileA(file_path.c_str()));
ASSERT_TRUE(::DeleteFileA(file_path2.c_str()));
ASSERT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
ASSERT_FALSE(::RemoveDirectoryA(dir_path.c_str()));
ASSERT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
TYPED_TEST(winfsp_test, delete_can_delete_on_close_after_mapping) {
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_3"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_3"}),
};
auto file_path2{
utils::path::combine(dir_path, {"test_file2_3"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto seed = static_cast<unsigned>(std::time(nullptr));
srand(seed);
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
{
auto handle = ::CreateFileA(
file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto *mapping =
::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
16U * sys_info.dwAllocationGranularity, nullptr);
EXPECT_TRUE(mapping != nullptr);
auto *view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
EXPECT_TRUE(view != nullptr);
for (PUINT8 ptr = reinterpret_cast<PUINT8>(view),
end = ptr + 16U * sys_info.dwAllocationGranularity;
end > ptr; ++ptr) {
*ptr = rand() & 0xFF;
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
EXPECT_TRUE(::CloseHandle(handle));
}
{
auto handle = ::CreateFileA(
file_path2.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto *mapping =
::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
16U * sys_info.dwAllocationGranularity, nullptr);
EXPECT_TRUE(mapping != nullptr);
auto *view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
EXPECT_TRUE(view != nullptr);
for (PUINT8 ptr = reinterpret_cast<PUINT8>(view),
end = ptr + 16U * sys_info.dwAllocationGranularity;
end > ptr; ++ptr) {
*ptr = rand() & 0xFF;
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
EXPECT_TRUE(::CloseHandle(handle));
}
ASSERT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
ASSERT_FALSE(::RemoveDirectoryA(dir_path.c_str()));
ASSERT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,406 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, info_can_get_tag_info) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
FILE_ATTRIBUTE_TAG_INFO tag_info{};
EXPECT_TRUE(::GetFileInformationByHandleEx(handle, FileAttributeTagInfo,
&tag_info, sizeof tag_info));
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE, tag_info.FileAttributes);
EXPECT_EQ(0, tag_info.ReparseTag);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_get_basic_info) {
FILETIME file_time{};
::GetSystemTimeAsFileTime(&file_time);
auto time_low = reinterpret_cast<PLARGE_INTEGER>(&file_time)->QuadPart;
auto time_high = time_low + 10000 * 10000 /* 10 seconds */;
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
FILE_BASIC_INFO basic_info{};
EXPECT_TRUE(::GetFileInformationByHandleEx(handle, FileBasicInfo, &basic_info,
sizeof basic_info));
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE, basic_info.FileAttributes);
EXPECT_LE(time_low, basic_info.CreationTime.QuadPart);
EXPECT_GT(time_high, basic_info.CreationTime.QuadPart);
EXPECT_LE(time_low, basic_info.LastAccessTime.QuadPart);
EXPECT_GT(time_high, basic_info.LastAccessTime.QuadPart);
EXPECT_LE(time_low, basic_info.LastWriteTime.QuadPart);
EXPECT_GT(time_high, basic_info.LastWriteTime.QuadPart);
EXPECT_LE(time_low, basic_info.ChangeTime.QuadPart);
EXPECT_GT(time_high, basic_info.ChangeTime.QuadPart);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_get_standard_info) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
FILE_STANDARD_INFO std_info{};
EXPECT_TRUE(::GetFileInformationByHandleEx(handle, FileStandardInfo,
&std_info, sizeof std_info));
EXPECT_EQ(0, std_info.AllocationSize.QuadPart);
EXPECT_EQ(0, std_info.EndOfFile.QuadPart);
EXPECT_EQ(1, std_info.NumberOfLinks);
EXPECT_FALSE(std_info.DeletePending);
EXPECT_FALSE(std_info.Directory);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_get_file_name_info) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
std::array<std::uint8_t, sizeof(FILE_NAME_INFO) + MAX_PATH> name_info{};
EXPECT_TRUE(
::GetFileInformationByHandleEx(handle, FileNameInfo, name_info.data(),
static_cast<DWORD>(name_info.size())));
auto *info = reinterpret_cast<FILE_NAME_INFO *>(name_info.data());
auto expected_name{
std::string{"\\repertory\\"} + this->mount_location.at(0U) +
"\\test_file_2",
};
EXPECT_EQ(info->FileNameLength,
static_cast<DWORD>(expected_name.size() * 2U));
EXPECT_STREQ(expected_name.c_str(),
utils::string::to_utf8(info->FileName).c_str());
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_get_file_name_info_buffer_too_small) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
std::array<std::uint8_t, sizeof(FILE_NAME_INFO)> name_info{};
EXPECT_FALSE(
::GetFileInformationByHandleEx(handle, FileNameInfo, name_info.data(),
static_cast<DWORD>(name_info.size())));
EXPECT_EQ(ERROR_MORE_DATA, ::GetLastError());
auto *info = reinterpret_cast<FILE_NAME_INFO *>(name_info.data());
EXPECT_EQ('\\', info->FileName[0U]);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_get_file_info) {
FILETIME file_time{};
::GetSystemTimeAsFileTime(&file_time);
auto time_low = reinterpret_cast<PLARGE_INTEGER>(&file_time)->QuadPart;
auto time_high = time_low + 10000 * 10000 /* 10 seconds */;
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_LE(
time_low,
reinterpret_cast<PLARGE_INTEGER>(&file_info.ftCreationTime)->QuadPart);
EXPECT_GT(
time_high,
reinterpret_cast<PLARGE_INTEGER>(&file_info.ftCreationTime)->QuadPart);
EXPECT_LE(
time_low,
reinterpret_cast<PLARGE_INTEGER>(&file_info.ftLastAccessTime)->QuadPart);
EXPECT_GT(
time_high,
reinterpret_cast<PLARGE_INTEGER>(&file_info.ftLastAccessTime)->QuadPart);
EXPECT_LE(
time_low,
reinterpret_cast<PLARGE_INTEGER>(&file_info.ftLastWriteTime)->QuadPart);
EXPECT_GT(
time_high,
reinterpret_cast<PLARGE_INTEGER>(&file_info.ftLastWriteTime)->QuadPart);
EXPECT_EQ(0U, file_info.nFileSizeHigh);
EXPECT_EQ(0U, file_info.nFileSizeLow);
EXPECT_EQ(1U, file_info.nNumberOfLinks);
EXPECT_EQ(FILE_ATTRIBUTE_ARCHIVE, file_info.dwFileAttributes);
EXPECT_EQ(0U, file_info.dwVolumeSerialNumber);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_get_file_path) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
std::string final_path;
final_path.resize(MAX_PATH + 1U);
auto result = ::GetFinalPathNameByHandleA(
handle, final_path.data(), final_path.size() - 1U,
VOLUME_NAME_NONE | FILE_NAME_OPENED);
auto expected_name{
std::string{"\\repertory\\"} + this->mount_location.at(0U) +
"\\test_file_2",
};
EXPECT_EQ(result, static_cast<DWORD>(expected_name.size()));
EXPECT_STREQ(expected_name.c_str(), final_path.c_str());
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_set_file_info_attributes_to_hidden) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
EXPECT_TRUE(::SetFileAttributesA(file_path.c_str(), FILE_ATTRIBUTE_HIDDEN));
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_EQ(FILE_ATTRIBUTE_HIDDEN, file_info.dwFileAttributes);
::CloseHandle(handle);
}
TYPED_TEST(
winfsp_test,
info_can_set_file_info_attributes_to_hidden_ignoring_directory_attribute) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
EXPECT_TRUE(::SetFileAttributesA(
file_path.c_str(), FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN));
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_EQ(FILE_ATTRIBUTE_HIDDEN, file_info.dwFileAttributes);
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_set_creation_file_time) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
BY_HANDLE_FILE_INFORMATION orig_file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &orig_file_info));
static constexpr const UINT64 file_time{
116444736000000000ULL + 0x4200000042ULL,
};
EXPECT_TRUE(::SetFileTime(handle,
reinterpret_cast<const FILETIME *>(&file_time),
nullptr, nullptr));
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_EQ(file_time, *reinterpret_cast<PUINT64>(&file_info.ftCreationTime));
EXPECT_EQ(*reinterpret_cast<PUINT64>(&orig_file_info.ftLastAccessTime),
*reinterpret_cast<PUINT64>(&file_info.ftLastAccessTime));
EXPECT_EQ(*reinterpret_cast<PUINT64>(&orig_file_info.ftLastWriteTime),
*reinterpret_cast<PUINT64>(&file_info.ftLastWriteTime));
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_set_accessed_file_time) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
BY_HANDLE_FILE_INFORMATION orig_file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &orig_file_info));
static constexpr const UINT64 file_time{
116444736000000000ULL + 0x4200000042ULL,
};
EXPECT_TRUE(::SetFileTime(handle, nullptr,
reinterpret_cast<const FILETIME *>(&file_time),
nullptr));
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_EQ(file_time, *reinterpret_cast<PUINT64>(&file_info.ftLastAccessTime));
EXPECT_EQ(*reinterpret_cast<PUINT64>(&orig_file_info.ftCreationTime),
*reinterpret_cast<PUINT64>(&file_info.ftCreationTime));
EXPECT_EQ(*reinterpret_cast<PUINT64>(&orig_file_info.ftLastWriteTime),
*reinterpret_cast<PUINT64>(&file_info.ftLastWriteTime));
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_set_written_file_time) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
BY_HANDLE_FILE_INFORMATION orig_file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &orig_file_info));
static constexpr const UINT64 file_time{
116444736000000000ULL + 0x4200000042ULL,
};
EXPECT_TRUE(::SetFileTime(handle, nullptr, nullptr,
reinterpret_cast<const FILETIME *>(&file_time)));
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_EQ(file_time, *reinterpret_cast<PUINT64>(&file_info.ftLastWriteTime));
EXPECT_EQ(*reinterpret_cast<PUINT64>(&orig_file_info.ftLastAccessTime),
*reinterpret_cast<PUINT64>(&file_info.ftLastAccessTime));
EXPECT_EQ(*reinterpret_cast<PUINT64>(&orig_file_info.ftCreationTime),
*reinterpret_cast<PUINT64>(&file_info.ftCreationTime));
::CloseHandle(handle);
}
TYPED_TEST(winfsp_test, info_can_set_file_size) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_2"}),
};
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto offset = ::SetFilePointer(handle, 42, nullptr, FILE_BEGIN);
EXPECT_EQ(42U, offset);
EXPECT_TRUE(::SetEndOfFile(handle));
BY_HANDLE_FILE_INFORMATION file_info{};
EXPECT_TRUE(::GetFileInformationByHandle(handle, &file_info));
EXPECT_EQ(0U, file_info.nFileSizeHigh);
EXPECT_EQ(42U, file_info.nFileSizeLow);
::CloseHandle(handle);
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,854 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
static void test_file(auto &&mount_location, auto &&file_path, auto &&flags) {
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
DWORD bytes_per_sector{};
DWORD free_clusters{};
DWORD sectors_per_cluster{};
DWORD total_clusters{};
EXPECT_TRUE(::GetDiskFreeSpaceA(mount_location.c_str(), &sectors_per_cluster,
&bytes_per_sector, &free_clusters,
&total_clusters));
const auto buffer_size = 16U * sys_info.dwPageSize;
auto write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | flags, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
DWORD bytes_written{};
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
pointer = ::SetFilePointer(handle, 2U * bytes_per_sector, 0, FILE_BEGIN);
EXPECT_EQ(2U * bytes_per_sector, pointer);
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(), bytes_per_sector,
&bytes_written, 0));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
data_buffer read_buffer{};
read_buffer.resize(buffer_size);
DWORD bytes_read{};
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read, ::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
for (auto idx = 0; idx < 2; ++idx) {
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle, 2U * bytes_per_sector, 0, FILE_BEGIN);
EXPECT_EQ(2U * bytes_per_sector, pointer);
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), bytes_per_sector,
&bytes_read, 0));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
}
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle, 3U * bytes_per_sector, 0, FILE_BEGIN);
EXPECT_EQ(3U * bytes_per_sector, pointer);
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(0U, bytes_read);
EXPECT_EQ(pointer + bytes_read, ::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_written, nullptr));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_read, 0U));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_read);
EXPECT_EQ(pointer + bytes_read, ::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_written, nullptr));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_read, nullptr));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_read);
EXPECT_EQ(pointer + bytes_read, ::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(),
2U * sys_info.dwPageSize + bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(2U * sys_info.dwPageSize + bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(),
2U * sys_info.dwPageSize + bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(2U * sys_info.dwPageSize + bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read, ::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle));
handle = ::CreateFileA(
file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | flags | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(),
2U * sys_info.dwPageSize + bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(2U * sys_info.dwPageSize + bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read, ::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle));
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, nullptr);
EXPECT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
static void test_append_file(auto &&mount_location, auto &&file_path,
auto &&flags, bool should_fail = false) {
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
DWORD bytes_per_sector{};
DWORD free_clusters{};
DWORD sectors_per_cluster{};
DWORD total_clusters{};
EXPECT_TRUE(::GetDiskFreeSpaceA(mount_location.c_str(), &sectors_per_cluster,
&bytes_per_sector, &free_clusters,
&total_clusters));
const auto buffer_size = 16U * sys_info.dwPageSize;
auto write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
auto handle =
CreateFileA(file_path.c_str(), FILE_APPEND_DATA, FILE_SHARE_READ, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL | flags, nullptr);
if (should_fail) {
EXPECT_EQ(INVALID_HANDLE_VALUE, handle);
return;
}
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
DWORD bytes_written{};
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_TRUE(::WriteFile(handle, write_buffer.data() + bytes_per_sector,
bytes_per_sector, &bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_TRUE(::CloseHandle(handle));
handle = CreateFileA(
file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | flags | FILE_FLAG_DELETE_ON_CLOSE, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
auto pointer = ::SetFilePointer(handle, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
data_buffer read_buffer{};
read_buffer.resize(buffer_size);
DWORD bytes_read{};
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), 2U * bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(2U * bytes_per_sector, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle));
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, nullptr);
EXPECT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
static void test_overlapped_file(auto &&mount_location, auto &&file_path,
auto &&flags) {
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
DWORD bytes_per_sector{};
DWORD free_clusters{};
DWORD sectors_per_cluster{};
DWORD total_clusters{};
EXPECT_TRUE(::GetDiskFreeSpaceA(mount_location.c_str(), &sectors_per_cluster,
&bytes_per_sector, &free_clusters,
&total_clusters));
const auto buffer_size = 16U * sys_info.dwPageSize;
auto write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
OVERLAPPED overlapped{};
overlapped.hEvent = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
ASSERT_NE(nullptr, overlapped.hEvent);
auto handle = ::CreateFileA(
file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | flags | FILE_FLAG_OVERLAPPED, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
overlapped.Offset = 0U;
DWORD bytes_written{};
auto ret = ::WriteFile(handle, write_buffer.data(), bytes_per_sector,
&bytes_written, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_written, TRUE));
EXPECT_EQ(bytes_per_sector, bytes_written);
overlapped.Offset = 2U * bytes_per_sector;
ret = ::WriteFile(handle, write_buffer.data(), bytes_per_sector,
&bytes_written, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_written, TRUE));
EXPECT_EQ(bytes_per_sector, bytes_written);
data_buffer read_buffer{};
read_buffer.resize(buffer_size);
overlapped.Offset = 0U;
DWORD bytes_read{};
ret = ::ReadFile(handle, read_buffer.data(), bytes_per_sector, &bytes_read,
&overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 2U * bytes_per_sector;
ret = ::ReadFile(handle, read_buffer.data(), bytes_per_sector, &bytes_read,
&overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 2U * bytes_per_sector;
ret = ::ReadFile(handle, read_buffer.data(), 2U * bytes_per_sector,
&bytes_read, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 3U * bytes_per_sector;
ret = ::ReadFile(handle, read_buffer.data(), bytes_per_sector, &bytes_read,
&overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError() ||
ERROR_HANDLE_EOF == ::GetLastError());
if (ERROR_HANDLE_EOF != ::GetLastError()) {
ret = ::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE);
EXPECT_FALSE(ret);
EXPECT_EQ(ERROR_HANDLE_EOF, ::GetLastError());
}
EXPECT_EQ(0U, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
overlapped.Offset = 0U;
ret = ::WriteFile(handle, write_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_written, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_written, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_written);
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 0U;
ret = ::ReadFile(handle, read_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_read, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
overlapped.Offset = 0U;
ret = ::WriteFile(handle, write_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_written, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_written, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_written);
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 0U;
ret = ::ReadFile(handle, read_buffer.data(), 2U * sys_info.dwPageSize,
&bytes_read, &overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
overlapped.Offset = 0U;
ret = ::WriteFile(handle, write_buffer.data(),
2U * sys_info.dwPageSize + bytes_per_sector, &bytes_written,
&overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_written, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize + bytes_per_sector, bytes_written);
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 0U;
ret = ::ReadFile(handle, read_buffer.data(),
2U * sys_info.dwPageSize + bytes_per_sector, &bytes_read,
&overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize + bytes_per_sector, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle));
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | flags | FILE_FLAG_OVERLAPPED |
FILE_FLAG_DELETE_ON_CLOSE,
nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
read_buffer.clear();
read_buffer.resize(buffer_size);
overlapped.Offset = 0U;
ret = ::ReadFile(handle, read_buffer.data(),
2U * sys_info.dwPageSize + bytes_per_sector, &bytes_read,
&overlapped);
EXPECT_TRUE(ret || ERROR_IO_PENDING == ::GetLastError());
EXPECT_TRUE(::GetOverlappedResult(handle, &overlapped, &bytes_read, TRUE));
EXPECT_EQ(2U * sys_info.dwPageSize + bytes_per_sector, bytes_read);
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle));
EXPECT_TRUE(::CloseHandle(overlapped.hEvent));
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, 0, nullptr);
EXPECT_EQ(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(ERROR_FILE_NOT_FOUND, ::GetLastError());
}
static void test_mixed_file(auto &&mount_location, auto &&file_path) {
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
DWORD bytes_per_sector{};
DWORD free_clusters{};
DWORD sectors_per_cluster{};
DWORD total_clusters{};
EXPECT_TRUE(::GetDiskFreeSpaceA(mount_location.c_str(), &sectors_per_cluster,
&bytes_per_sector, &free_clusters,
&total_clusters));
const auto buffer_size = 16U * sys_info.dwPageSize;
auto write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
auto handle0 = CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle0);
auto handle1 =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle0);
auto pointer = ::SetFilePointer(handle0, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
DWORD bytes_written{};
EXPECT_TRUE(::WriteFile(handle0, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_per_sector,
::SetFilePointer(handle0, 0, 0, FILE_CURRENT));
pointer = ::SetFilePointer(handle1, 2 * bytes_per_sector, 0, FILE_BEGIN);
EXPECT_EQ(2U * bytes_per_sector, pointer);
EXPECT_TRUE(::WriteFile(handle1, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle1, 0, 0, FILE_CURRENT));
pointer = ::SetFilePointer(handle0, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
data_buffer read_buffer{};
read_buffer.resize(buffer_size);
DWORD bytes_read{};
EXPECT_TRUE(::ReadFile(handle0, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read,
::SetFilePointer(handle0, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
read_buffer.clear();
read_buffer.resize(buffer_size);
pointer = ::SetFilePointer(handle1, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::ReadFile(handle1, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read,
::SetFilePointer(handle1, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle0));
EXPECT_TRUE(::CloseHandle(handle1));
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
handle0 = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
EXPECT_NE(INVALID_HANDLE_VALUE, handle0);
pointer = ::SetFilePointer(handle0, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
EXPECT_TRUE(::WriteFile(handle0, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle0, 0, 0, FILE_CURRENT));
pointer = ::SetFilePointer(handle0, 2 * bytes_per_sector, 0, FILE_BEGIN);
EXPECT_EQ(2U * bytes_per_sector, pointer);
EXPECT_TRUE(::WriteFile(handle0, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle0, 0, 0, FILE_CURRENT));
pointer = ::SetFilePointer(handle0, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
read_buffer.clear();
read_buffer.resize(buffer_size);
EXPECT_TRUE(::ReadFile(handle0, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read,
::SetFilePointer(handle0, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle0));
handle1 =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING, nullptr);
EXPECT_NE(INVALID_HANDLE_VALUE, handle0);
pointer = ::SetFilePointer(handle1, 0, 0, FILE_BEGIN);
EXPECT_EQ(0U, pointer);
read_buffer.clear();
read_buffer.resize(buffer_size);
EXPECT_TRUE(::ReadFile(handle1, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read,
::SetFilePointer(handle1, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
EXPECT_TRUE(::CloseHandle(handle1));
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
}
static void test_mmap_file(auto &&mount_location, auto &&file_path,
auto &&flags, auto &&early_close) {
SYSTEM_INFO sys_info{};
::GetSystemInfo(&sys_info);
DWORD bytes_per_sector{};
DWORD free_clusters{};
DWORD sectors_per_cluster{};
DWORD total_clusters{};
EXPECT_TRUE(::GetDiskFreeSpaceA(mount_location.c_str(), &sectors_per_cluster,
&bytes_per_sector, &free_clusters,
&total_clusters));
const auto buffer_size = 16U * sys_info.dwPageSize;
auto write_buffer = utils::generate_secure_random<data_buffer>(buffer_size);
auto handle =
::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | flags, nullptr);
EXPECT_NE(INVALID_HANDLE_VALUE, handle);
DWORD file_size0{
2U * sys_info.dwAllocationGranularity,
};
DWORD file_size1{100U};
auto *mapping = ::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
file_size0 + file_size1, nullptr);
ASSERT_NE(nullptr, mapping);
if (early_close) {
EXPECT_TRUE(::CloseHandle(handle));
}
auto *view = MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ASSERT_NE(nullptr, view);
auto seed = static_cast<unsigned>(std::time(nullptr));
srand(seed);
for (PUINT8 begin = reinterpret_cast<PUINT8>(view) + file_size1 / 2U,
end = begin + file_size0;
end > begin; ++begin) {
*begin = rand() & 0xFF;
}
EXPECT_TRUE(::UnmapViewOfFile(view));
view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ASSERT_NE(nullptr, view);
srand(seed);
for (PUINT8 begin = reinterpret_cast<PUINT8>(view) + file_size1 / 2U,
end = begin + file_size0;
end > begin; begin++) {
EXPECT_EQ(*begin, (rand() & 0xFF));
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
if (not early_close) {
EXPECT_TRUE(::CloseHandle(handle));
}
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | flags, nullptr);
EXPECT_NE(INVALID_HANDLE_VALUE, handle);
mapping = ::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
file_size0 + file_size1, nullptr);
ASSERT_NE(nullptr, mapping);
if (early_close) {
EXPECT_TRUE(::CloseHandle(handle));
}
view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ASSERT_NE(nullptr, view);
srand(seed);
for (PUINT8 begin = reinterpret_cast<PUINT8>(view) + file_size1 / 2U,
end = begin + file_size0;
end > begin; ++begin) {
EXPECT_EQ(*begin, (rand() & 0xFF));
}
if (not early_close) {
auto pointer = ::SetFilePointer(handle, file_size0 / 2U, 0, FILE_BEGIN);
EXPECT_EQ(file_size0 / 2U, pointer);
DWORD bytes_written{};
EXPECT_TRUE(::WriteFile(handle, write_buffer.data(), bytes_per_sector,
&bytes_written, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_written);
EXPECT_EQ(pointer + bytes_written,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
if (not early_close) {
EXPECT_TRUE(::CloseHandle(handle));
}
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | flags, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
mapping = ::CreateFileMappingA(handle, nullptr, PAGE_READWRITE, 0,
file_size0 + file_size1, nullptr);
ASSERT_NE(nullptr, mapping);
if (early_close) {
EXPECT_TRUE(::CloseHandle(handle));
}
view = ::MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
ASSERT_NE(nullptr, view);
if (not early_close) {
auto pointer = ::SetFilePointer(handle, file_size0 / 2U, 0, FILE_BEGIN);
EXPECT_EQ(file_size0 / 2U, pointer);
data_buffer read_buffer{};
read_buffer.resize(buffer_size);
DWORD bytes_read{};
EXPECT_TRUE(::ReadFile(handle, read_buffer.data(), bytes_per_sector,
&bytes_read, nullptr));
EXPECT_EQ(bytes_per_sector, bytes_read);
EXPECT_EQ(pointer + bytes_read,
::SetFilePointer(handle, 0, 0, FILE_CURRENT));
EXPECT_EQ(0,
std::memcmp(write_buffer.data(), read_buffer.data(), bytes_read));
}
srand(seed);
for (PUINT8 begin = reinterpret_cast<PUINT8>(view) + file_size1 / 2U,
end = reinterpret_cast<PUINT8>(view) + file_size0 / 2U;
end > begin; begin++) {
EXPECT_EQ(*begin, (rand() & 0xFF));
}
if (not early_close) {
EXPECT_EQ(0, std::memcmp(write_buffer.data(), view + file_size0 / 2U,
bytes_per_sector));
}
for (size_t idx = 0U; bytes_per_sector > idx; ++idx) {
rand();
}
for (PUINT8
begin = reinterpret_cast<PUINT8>(view) + file_size0 / 2U +
bytes_per_sector,
end = reinterpret_cast<PUINT8>(view) + file_size1 / 2U + file_size0;
end > begin; ++begin) {
EXPECT_EQ(*begin, (rand() & 0xFF));
}
EXPECT_TRUE(::UnmapViewOfFile(view));
EXPECT_TRUE(::CloseHandle(mapping));
if (not early_close) {
EXPECT_TRUE(::CloseHandle(handle));
}
EXPECT_TRUE(DeleteFileA(file_path.c_str()));
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_file_no_flags) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_file(this->mount_location, file_path, 0U);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_file_no_buffering) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_file(this->mount_location, file_path, FILE_FLAG_NO_BUFFERING);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_file_write_through) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_file(this->mount_location, file_path, FILE_FLAG_WRITE_THROUGH);
}
TYPED_TEST(winfsp_test, rdrw_can_append_file_no_flags) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_append_file(this->mount_location, file_path, 0U);
}
TYPED_TEST(winfsp_test, rdrw_can_append_file_no_buffering) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_append_file(this->mount_location, file_path, FILE_FLAG_NO_BUFFERING,
true);
}
TYPED_TEST(winfsp_test, rdrw_can_append_file_write_through) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_append_file(this->mount_location, file_path, FILE_FLAG_WRITE_THROUGH);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_overlapped_file_no_flags) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_overlapped_file(this->mount_location, file_path, 0U);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_overlapped_file_no_buffering) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_overlapped_file(this->mount_location, file_path, FILE_FLAG_NO_BUFFERING);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_file_overlapped_write_through) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_overlapped_file(this->mount_location, file_path,
FILE_FLAG_WRITE_THROUGH);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_mmap_file_no_flags) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_mmap_file(this->mount_location, file_path, 0U, false);
test_mmap_file(this->mount_location, file_path, 0U, true);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_mmap_file_no_buffering) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_mmap_file(this->mount_location, file_path, FILE_FLAG_NO_BUFFERING,
false);
test_mmap_file(this->mount_location, file_path, FILE_FLAG_NO_BUFFERING, true);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_file_mmap_write_through) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_mmap_file(this->mount_location, file_path, FILE_FLAG_WRITE_THROUGH,
false);
test_mmap_file(this->mount_location, file_path, FILE_FLAG_WRITE_THROUGH,
true);
}
TYPED_TEST(winfsp_test, rdrw_can_read_and_write_mixed_file) {
auto file_path{
utils::path::combine(this->mount_location, {"test_file_5"}),
};
test_mixed_file(this->mount_location, file_path);
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,260 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, rename_can_rename_file_if_dest_does_not_exist) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_4"}),
};
auto file_path2{
utils::path::combine(dir_path, {"test_file2_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_TRUE(::MoveFileExA(file_path.c_str(), file_path2.c_str(), 0));
EXPECT_TRUE(::DeleteFileA(file_path2.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
}
TYPED_TEST(winfsp_test, rename_fails_if_dest_exists_and_replace_is_false) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_4"}),
};
auto file_path2{
utils::path::combine(dir_path, {"test_file2_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_TRUE(::MoveFileExA(file_path.c_str(), file_path2.c_str(), 0));
EXPECT_FALSE(::MoveFileExA(file_path.c_str(), file_path2.c_str(), 0));
EXPECT_EQ(ERROR_ALREADY_EXISTS, ::GetLastError());
EXPECT_TRUE(::DeleteFileA(file_path2.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
}
TYPED_TEST(winfsp_test, rename_succeeds_if_dest_exists_and_replace_is_true) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_4"}),
};
auto file_path2{
utils::path::combine(dir_path, {"test_file2_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_TRUE(::MoveFileExA(file_path.c_str(), file_path2.c_str(), 0));
handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_TRUE(::MoveFileExA(file_path.c_str(), file_path2.c_str(),
MOVEFILE_REPLACE_EXISTING));
EXPECT_TRUE(::DeleteFileA(file_path2.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
}
TYPED_TEST(winfsp_test, rename_can_rename_dir_if_dest_does_not_exist) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto dir_path2{
utils::path::combine(this->mount_location, {"test_dir2_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
EXPECT_TRUE(::MoveFileExA(dir_path.c_str(), dir_path2.c_str(), 0));
EXPECT_TRUE(::RemoveDirectoryA(dir_path2.c_str()));
}
TYPED_TEST(winfsp_test, rename_dir_fails_if_dest_exists_and_replace_is_false) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto dir_path2{
utils::path::combine(this->mount_location, {"test_dir2_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
ASSERT_TRUE(::CreateDirectoryA(dir_path2.c_str(), nullptr));
EXPECT_FALSE(::MoveFileExA(dir_path.c_str(), dir_path2.c_str(), 0));
EXPECT_EQ(ERROR_ACCESS_DENIED, ::GetLastError());
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path2.c_str()));
}
TYPED_TEST(winfsp_test, rename_dir_fails_if_dest_exists_and_replace_is_true) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto dir_path2{
utils::path::combine(this->mount_location, {"test_dir2_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
ASSERT_TRUE(::CreateDirectoryA(dir_path2.c_str(), nullptr));
EXPECT_FALSE(::MoveFileExA(dir_path.c_str(), dir_path2.c_str(),
MOVEFILE_REPLACE_EXISTING));
EXPECT_EQ(ERROR_ACCESS_DENIED, ::GetLastError());
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path2.c_str()));
}
TYPED_TEST(winfsp_test,
rename_dir_fails_directory_is_not_empty_and_replace_is_false) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto dir_path2{
utils::path::combine(this->mount_location, {"test_dir2_4"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_FALSE(::MoveFileExA(dir_path.c_str(), dir_path2.c_str(), 0));
EXPECT_EQ(ERROR_ACCESS_DENIED, ::GetLastError());
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
}
TYPED_TEST(winfsp_test,
rename_dir_fails_directory_is_not_empty_and_replace_is_true) {
if (this->current_provider == provider_type::s3) {
return;
}
auto dir_path{
utils::path::combine(this->mount_location, {"test_dir_4"}),
};
auto dir_path2{
utils::path::combine(this->mount_location, {"test_dir2_4"}),
};
auto file_path{
utils::path::combine(dir_path, {"test_file_4"}),
};
ASSERT_TRUE(::CreateDirectoryA(dir_path.c_str(), nullptr));
auto handle = ::CreateFileA(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
::CloseHandle(handle);
EXPECT_FALSE(::MoveFileExA(dir_path.c_str(), dir_path2.c_str(),
MOVEFILE_REPLACE_EXISTING));
EXPECT_EQ(ERROR_ACCESS_DENIED, ::GetLastError());
EXPECT_TRUE(::DeleteFileA(file_path.c_str()));
EXPECT_TRUE(::RemoveDirectoryA(dir_path.c_str()));
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,61 @@
/*
Copyright <2018-2024> <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)
// TODO revisit create_allocation
// TODO revisit create_backup
// TODO revisit create_notraverse
// TODO revisit create_related
// TODO revisit create_restore
// TODO revisit create_sd
// TODO revisit create_share
// TODO revisit delete_access_test
// TODO revisit delete_ex_test
// TODO revisit getfileattr_test
// TODO revisit query_winfsp_test
// TODO revisit rename_backslash_test
// TODO revisit rename_caseins_test
// TODO revisit rename_ex_test
// TODO revisit rename_flipflop_test
// TODO revisit rename_mmap_test
// TODO revisit rename_open_test
// TODO revisit rename_pid_test
// TODO revisit rename_standby_test
// TODO revisit setvolinfo_test
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, can_set_current_directory_to_mount_location) {
EXPECT_TRUE(::SetCurrentDirectoryA(this->mount_location.c_str()));
EXPECT_TRUE(::SetCurrentDirectoryA(this->current_directory.string().c_str()));
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1,100 @@
/*
Copyright <2018-2024> <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)
//
// Implemented test cases based on WinFsp tests:
// https://github.com/winfsp/winfsp/blob/v2.0/tst/winfsp-tests
//
#include "fixtures/winfsp_fixture.hpp"
namespace repertory {
TYPED_TEST_CASE(winfsp_test, winfsp_provider_types);
TYPED_TEST(winfsp_test, volume_can_get_volume_info) {
std::string volume_label;
volume_label.resize(MAX_PATH + 1U);
std::string fs_name;
fs_name.resize(MAX_PATH + 1U);
DWORD flags{};
DWORD max_component_length{};
DWORD serial_num{};
EXPECT_TRUE(::GetVolumeInformationA(
this->mount_location.c_str(), volume_label.data(),
static_cast<DWORD>(volume_label.size()), &serial_num,
&max_component_length, &flags, fs_name.data(),
static_cast<DWORD>(fs_name.size())));
EXPECT_EQ(FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES |
FILE_UNICODE_ON_DISK,
flags);
EXPECT_EQ(255U, max_component_length);
EXPECT_EQ(0U, serial_num);
EXPECT_STREQ(
("repertory_" + app_config::get_provider_name(this->current_provider))
.c_str(),
volume_label.c_str());
EXPECT_STREQ(this->mount_location.c_str(), fs_name.c_str());
}
TYPED_TEST(winfsp_test, volume_can_get_size_info) {
{
DWORD bytes_per_sector{};
DWORD free_clusters{};
DWORD sectors_per_cluster{};
DWORD total_clusters{};
EXPECT_TRUE(::GetDiskFreeSpaceA(this->mount_location.c_str(),
&sectors_per_cluster, &bytes_per_sector,
&free_clusters, &total_clusters));
EXPECT_NE(0U, bytes_per_sector);
EXPECT_NE(0U, free_clusters);
EXPECT_NE(0U, sectors_per_cluster);
EXPECT_NE(0U, total_clusters);
}
{
ULARGE_INTEGER caller_free_bytes{};
ULARGE_INTEGER free_bytes{};
ULARGE_INTEGER total_bytes{};
EXPECT_TRUE(::GetDiskFreeSpaceExA(this->mount_location.c_str(),
&caller_free_bytes, &total_bytes,
&free_bytes));
EXPECT_NE(0U, caller_free_bytes.QuadPart);
EXPECT_NE(0U, total_bytes.QuadPart);
EXPECT_NE(0U, free_bytes.QuadPart);
}
}
TYPED_TEST(winfsp_test, volume_can_get_file_type) {
auto handle = ::CreateFileA(
this->mount_location.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
ASSERT_NE(INVALID_HANDLE_VALUE, handle);
EXPECT_EQ(FILE_TYPE_DISK, ::GetFileType(handle));
::CloseHandle(handle);
}
} // namespace repertory
#endif // defined(_WIN32)

View File

@@ -0,0 +1 @@
test

View File

@@ -0,0 +1 @@
moose