mirror of
https://github.com/basiliscos/syncspirit.git
synced 2026-06-01 17:47:32 +00:00
2308 lines
97 KiB
C++
2308 lines
97 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
// SPDX-FileCopyrightText: 2025-2026 Ivan Baidakou
|
||
|
||
#include "access.h"
|
||
#include "diff-builder.h"
|
||
#include "config/fs.h"
|
||
#include "hasher/hasher_plugin.h"
|
||
#include "constants.h"
|
||
#include "fs/fs_proxy.h"
|
||
#include "fs/fs_slave.h"
|
||
#include "fs/messages.h"
|
||
#include "fs/utils.h"
|
||
#include "fs/updates_mediator.h"
|
||
#include "managed_hasher.h"
|
||
#include "model/cluster.h"
|
||
#include "model/diff/advance/local_update.h"
|
||
#include "model/diff/local/file_availability.h"
|
||
#include "model/diff/load/interrupt.h"
|
||
#include "net/local_keeper.h"
|
||
#include "net/names.h"
|
||
#include "test-utils.h"
|
||
#include "test_supervisor.h"
|
||
#include "access.h"
|
||
#include "utils/platform.h"
|
||
#include "presentation/folder_entity.h"
|
||
#include <chrono>
|
||
#include <boost/nowide/convert.hpp>
|
||
#include <catch2/generators/catch_generators.hpp>
|
||
|
||
#ifndef SYNCSPIRIT_WIN
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#endif
|
||
|
||
using namespace syncspirit;
|
||
using namespace syncspirit::test;
|
||
using namespace syncspirit::model;
|
||
using namespace syncspirit::net;
|
||
using namespace syncspirit::fs;
|
||
using namespace syncspirit::hasher;
|
||
using boost::nowide::narrow;
|
||
using boost::nowide::widen;
|
||
|
||
using task_processor_t = std::function<void(fs::fs_slave_t *)>;
|
||
|
||
struct executor_t : r::actor_base_t {
|
||
using parent_t = r::actor_base_t;
|
||
using parent_t::parent_t;
|
||
|
||
// clang-format off
|
||
using plugins_list_t = std::tuple<
|
||
r::plugin::address_maker_plugin_t,
|
||
r::plugin::lifetime_plugin_t,
|
||
r::plugin::init_shutdown_plugin_t,
|
||
r::plugin::link_server_plugin_t,
|
||
r::plugin::link_client_plugin_t,
|
||
hasher::hasher_plugin_t,
|
||
r::plugin::resources_plugin_t,
|
||
r::plugin::starter_plugin_t
|
||
>;
|
||
// clang-format on
|
||
|
||
void configure(r::plugin::plugin_base_t &plugin) noexcept {
|
||
r::actor_base_t::configure(plugin);
|
||
plugin.with_casted<hasher::hasher_plugin_t>([&](auto &p) {
|
||
hasher = &p;
|
||
p.configure_hashers(1);
|
||
});
|
||
}
|
||
|
||
hasher_plugin_t *hasher;
|
||
};
|
||
|
||
using executor_ptr_t = r::intrusive_ptr_t<executor_t>;
|
||
|
||
struct fixture_t;
|
||
|
||
struct my_supervisort_t : supervisor_t {
|
||
using parent_t = supervisor_t;
|
||
using parent_t::parent_t;
|
||
|
||
fixture_t *fixture = nullptr;
|
||
|
||
void on_model_update(model::message::model_update_t &) noexcept override;
|
||
outcome::result<void> operator()(const model::diff::advance::local_update_t &, void *) noexcept override;
|
||
outcome::result<void> operator()(const model::diff::local::file_availability_t &, void *) noexcept override;
|
||
};
|
||
|
||
struct fixture_t {
|
||
using target_ptr_t = r::intrusive_ptr_t<net::local_keeper_t>;
|
||
using builder_ptr_t = std::unique_ptr<diff_builder_t>;
|
||
|
||
fixture_t(bool auto_launch_ = true) noexcept
|
||
: root_path{unique_path()}, path_guard{root_path}, auto_launch{auto_launch_} {
|
||
bfs::create_directory(root_path);
|
||
mediator = new fs::updates_mediator_t(pt::microseconds{1});
|
||
}
|
||
|
||
virtual std::uint32_t get_hash_limit() { return 1; }
|
||
|
||
bool execute_slave(fs::payload::foreign_executor_t &slave) {
|
||
static constexpr auto retension = pt::milliseconds{1};
|
||
auto deadline = pt::microsec_clock::local_time() + retension;
|
||
auto fs_proxy = fs_proxy_t(*mediator, deadline);
|
||
auto ctx = execution_context_t();
|
||
ctx.fs_proxy = &fs_proxy;
|
||
ctx.plugin = executor->hasher;
|
||
return slave.exec(ctx);
|
||
}
|
||
|
||
virtual void execute(fs::message::foreign_executor_t &req) noexcept {
|
||
auto slave = static_cast<fs::fs_slave_t *>(req.payload.get());
|
||
slave->ec = {};
|
||
sup->log->info("executing foreign task: {}", execute_slave(*req.payload));
|
||
}
|
||
|
||
virtual void create_dir(fs::message::create_dir_t &req) noexcept {
|
||
auto &path = req.payload;
|
||
sup->log->info("on_create_dir, '{}'", narrow(path.wstring()));
|
||
bfs::create_directory(path, path.ec);
|
||
}
|
||
|
||
virtual void launch_hasher() noexcept {
|
||
hasher = sup->create_actor<managed_hasher_t>().index(1).auto_reply(true).timeout(timeout).finish().get();
|
||
}
|
||
|
||
virtual void on_diff(const model::diff::advance::local_update_t &) noexcept {}
|
||
virtual void on_diff(const model::diff::local::file_availability_t &) noexcept {}
|
||
virtual void on_model_update(model::message::model_update_t &) noexcept {}
|
||
|
||
void run() noexcept {
|
||
sequencer = make_sequencer(1234);
|
||
auto my_hash = "KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD";
|
||
auto my_id = device_id_t::from_string(my_hash).value();
|
||
my_device = device_t::create(my_id, "my-device").value();
|
||
|
||
auto peer_hash = "VUV42CZ-IQD5A37-RPEBPM4-VVQK6E4-6WSKC7B-PVJQHHD-4PZD44V-ENC6WAZ";
|
||
auto peer_id = device_id_t::from_string(peer_hash).value();
|
||
peer_device = device_t::create(peer_id, "peer-device").value();
|
||
|
||
cluster = new cluster_t(my_device, 1);
|
||
|
||
cluster->get_devices().put(my_device);
|
||
cluster->get_devices().put(peer_device);
|
||
|
||
r::system_context_t ctx;
|
||
sup = ctx.create_supervisor<my_supervisort_t>()
|
||
.make_presentation(true)
|
||
.timeout(timeout)
|
||
.create_registry()
|
||
.finish();
|
||
sup->cluster = cluster;
|
||
static_cast<my_supervisort_t *>(sup.get())->fixture = this;
|
||
sup->configure_callback = [&](r::plugin::plugin_base_t &plugin) {
|
||
plugin.template with_casted<r::plugin::registry_plugin_t>(
|
||
[&](auto &p) { p.register_name(net::names::fs_actor, sup->get_address()); });
|
||
plugin.template with_casted<r::plugin::starter_plugin_t>([&](auto &p) {
|
||
using exec_msg_t = fs::message::foreign_executor_t;
|
||
using create_dir_msg_t = fs::message::create_dir_t;
|
||
|
||
p.subscribe_actor(r::lambda<exec_msg_t>([&](exec_msg_t &req) { execute(req); }));
|
||
p.subscribe_actor(r::lambda<create_dir_msg_t>([&](create_dir_msg_t &req) { create_dir(req); }));
|
||
});
|
||
};
|
||
|
||
auto folder_id = "1234-5678";
|
||
|
||
sup->start();
|
||
sup->do_process();
|
||
builder = std::make_unique<diff_builder_t>(*cluster);
|
||
builder->upsert_folder(folder_id, root_path)
|
||
.apply(*sup)
|
||
.share_folder(peer_id.get_sha256(), folder_id)
|
||
.apply(*sup);
|
||
|
||
folder = cluster->get_folders().by_id(folder_id);
|
||
folder_info = folder->get_folder_infos().by_device(*my_device);
|
||
files = &folder_info->get_file_infos();
|
||
folder_info_peer = folder->get_folder_infos().by_device(*peer_device);
|
||
files_peer = &folder_info_peer->get_file_infos();
|
||
|
||
CHECK(static_cast<r::actor_base_t *>(sup.get())->access<to::state>() == r::state_t::OPERATIONAL);
|
||
|
||
executor = sup->create_actor<executor_t>().timeout(timeout).finish();
|
||
launch_hasher();
|
||
sup->do_process();
|
||
|
||
auto fs_config = config::fs_config_t{3600, 10};
|
||
|
||
if (auto_launch) {
|
||
launch_target();
|
||
}
|
||
|
||
main();
|
||
|
||
sup->do_process();
|
||
sup->shutdown();
|
||
sup->do_process();
|
||
|
||
CHECK(static_cast<r::actor_base_t *>(sup.get())->access<to::state>() == r::state_t::SHUT_DOWN);
|
||
}
|
||
|
||
void launch_target() {
|
||
target = sup->create_actor<net::local_keeper_t>()
|
||
.timeout(timeout)
|
||
.sequencer(sequencer)
|
||
.concurrent_hashes(get_hash_limit())
|
||
.finish();
|
||
sup->do_process();
|
||
|
||
sup->send<syncspirit::model::payload::thread_ready_t>(sup->get_address(), cluster, std::this_thread::get_id());
|
||
sup->do_process();
|
||
}
|
||
|
||
virtual void main() noexcept {}
|
||
|
||
std::int64_t files_scan_iteration_limit = 100;
|
||
builder_ptr_t builder;
|
||
r::pt::time_duration timeout = r::pt::millisec{10};
|
||
r::intrusive_ptr_t<supervisor_t> sup;
|
||
executor_ptr_t executor;
|
||
managed_hasher_t *hasher;
|
||
cluster_ptr_t cluster;
|
||
device_ptr_t my_device;
|
||
bfs::path root_path;
|
||
test::path_guard_t path_guard;
|
||
target_ptr_t target;
|
||
updates_mediator_ptr_t mediator;
|
||
model::folder_ptr_t folder;
|
||
model::folder_info_ptr_t folder_info;
|
||
model::folder_info_ptr_t folder_info_peer;
|
||
model::file_infos_map_t *files;
|
||
model::file_infos_map_t *files_peer;
|
||
model::device_ptr_t peer_device;
|
||
model::sequencer_ptr_t sequencer;
|
||
bool auto_launch;
|
||
};
|
||
|
||
auto my_supervisort_t::operator()(const model::diff::advance::local_update_t &diff, void *custom) noexcept
|
||
-> outcome::result<void> {
|
||
fixture->on_diff(diff);
|
||
return parent_t::operator()(diff, custom);
|
||
}
|
||
|
||
auto my_supervisort_t::operator()(const model::diff::local::file_availability_t &diff, void *custom) noexcept
|
||
-> outcome::result<void> {
|
||
fixture->on_diff(diff);
|
||
return parent_t::operator()(diff, custom);
|
||
}
|
||
|
||
void my_supervisort_t::on_model_update(model::message::model_update_t &diff) noexcept {
|
||
fixture->on_model_update(diff);
|
||
return parent_t::on_model_update(diff);
|
||
}
|
||
|
||
void test_simple() {
|
||
struct F : fixture_t {
|
||
std::uint32_t get_hash_limit() override { return 2; }
|
||
|
||
void main() noexcept override {
|
||
sys::error_code ec;
|
||
auto &blocks = cluster->get_blocks();
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
SECTION("root folder errors") {
|
||
auto dir_path = root_path / "some-dir";
|
||
folder->set_path(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->is_suspended());
|
||
REQUIRE(folder->get_suspend_reason().message() != "");
|
||
SECTION("scan on created again") {
|
||
bfs::create_directories(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(!folder->is_suspended());
|
||
REQUIRE(!folder->get_suspend_reason());
|
||
}
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
SECTION("emtpy root dir") {
|
||
auto dir_path = root_path / "some-dir";
|
||
bfs::create_directories(dir_path);
|
||
folder->set_path(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
SECTION("new items") {
|
||
SECTION("new dir") {
|
||
auto dir_path = root_path / "some-dir";
|
||
bfs::create_directories(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file = files->by_name("some-dir");
|
||
REQUIRE(file);
|
||
CHECK(file->is_locally_available());
|
||
CHECK(file->is_dir());
|
||
CHECK(!file->is_link());
|
||
CHECK(file->get_block_size() == 0);
|
||
CHECK(file->get_size() == 0);
|
||
CHECK(cluster->get_blocks().size() == 0);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
SECTION("new dir inside a new ir") {
|
||
auto name_1 = bfs::path(L"п1");
|
||
auto name_2 = name_1 / bfs::path(L"п2");
|
||
auto dir_path = root_path / name_2;
|
||
bfs::create_directories(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
REQUIRE(cluster->get_blocks().size() == 0);
|
||
|
||
auto f_1 = files->by_name(narrow(name_1.generic_wstring()));
|
||
REQUIRE(f_1);
|
||
CHECK(f_1->is_locally_available());
|
||
CHECK(f_1->is_dir());
|
||
|
||
auto f_2 = files->by_name(narrow(name_2.generic_wstring()));
|
||
REQUIRE(f_2);
|
||
CHECK(f_2->is_locally_available());
|
||
CHECK(f_2->is_dir());
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
SECTION("empty file") {
|
||
CHECK(bfs::create_directories(root_path / "abc"));
|
||
auto file_path = root_path / "abc" / "empty.file";
|
||
write_file(file_path, "");
|
||
auto ingore_perms = GENERATE(0, 1);
|
||
auto folder_data = static_cast<model::folder_data_t *>(folder.get());
|
||
folder_data->access<test::to::ignore_permissions>() = (bool)ingore_perms;
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file = files->by_name("abc/empty.file");
|
||
REQUIRE(file);
|
||
CHECK(file->is_locally_available());
|
||
CHECK(!file->is_link());
|
||
CHECK(file->is_file());
|
||
CHECK(file->get_block_size() == 0);
|
||
CHECK(file->get_size() == 0);
|
||
CHECK(blocks.size() == 0);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
#ifndef SYNCSPIRIT_WIN
|
||
if (ingore_perms) {
|
||
CHECK(file->has_no_permissions());
|
||
CHECK(file->get_permissions() == 0666);
|
||
} else {
|
||
CHECK(!file->has_no_permissions());
|
||
CHECK(file->get_permissions() == static_cast<uint32_t>(bfs::status(file_path).permissions()));
|
||
}
|
||
#else
|
||
CHECK(file->has_no_permissions());
|
||
CHECK(file->get_permissions() == 0666);
|
||
#endif
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("new symlink") {
|
||
auto file_path = root_path / "symlink";
|
||
auto target = std::string_view("/some/where");
|
||
bfs::create_symlink(bfs::path(target), file_path, ec);
|
||
REQUIRE(!ec);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file = files->by_name("symlink");
|
||
REQUIRE(file);
|
||
CHECK(file->is_locally_available());
|
||
CHECK(!file->is_file());
|
||
CHECK(file->is_link());
|
||
CHECK(file->get_block_size() == 0);
|
||
CHECK(file->get_size() == 0);
|
||
CHECK(file->get_link_target() == target);
|
||
CHECK(file->has_no_permissions());
|
||
CHECK(blocks.size() == 0);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
#endif
|
||
SECTION("small non-emtpy file") {
|
||
CHECK(bfs::create_directories(root_path / L"папка"));
|
||
auto part_path = bfs::path(L"папка") / L"файл.bin";
|
||
auto file_path = root_path / part_path;
|
||
write_file(file_path, "12345");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file = files->by_name(narrow(part_path.generic_wstring()));
|
||
REQUIRE(file);
|
||
CHECK(file->is_locally_available());
|
||
CHECK(!file->is_link());
|
||
CHECK(file->is_file());
|
||
CHECK(file->get_block_size() == 5);
|
||
CHECK(file->get_size() == 5);
|
||
CHECK(blocks.size() == 1);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
SECTION("2 blocks file") {
|
||
CHECK(bfs::create_directories(root_path / L"папка"));
|
||
auto part_path = bfs::path(L"папка") / L"файл.bin";
|
||
auto file_path = root_path / part_path;
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto b1 = std::string(block_sz, '0');
|
||
auto b2 = std::string(block_sz, '1');
|
||
write_file(file_path, b1 + b2);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file = files->by_name(narrow(part_path.generic_wstring()));
|
||
REQUIRE(file);
|
||
CHECK(file->is_locally_available());
|
||
CHECK(!file->is_link());
|
||
CHECK(file->is_file());
|
||
CHECK(file->get_block_size() == block_sz);
|
||
CHECK(file->get_size() == block_sz * 2);
|
||
CHECK(blocks.size() == 2);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
SECTION("2 blocks + 1 byte file") {
|
||
CHECK(bfs::create_directories(root_path / L"папка"));
|
||
auto part_path = bfs::path(L"папка") / L"файл.bin";
|
||
auto file_path = root_path / part_path;
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto b1 = std::string(block_sz, '0');
|
||
auto b2 = std::string(block_sz, '1');
|
||
auto b3 = std::string(1, '2');
|
||
write_file(file_path, b1 + b2 + b3);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file = files->by_name(narrow(part_path.generic_wstring()));
|
||
REQUIRE(file);
|
||
CHECK(file->is_locally_available());
|
||
CHECK(!file->is_link());
|
||
CHECK(file->is_file());
|
||
CHECK(file->get_block_size() == block_sz);
|
||
CHECK(file->get_size() == block_sz * 2 + 1);
|
||
CHECK(blocks.size() == 3);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_create_dir() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
auto folder_id = std::string(folder->get_id());
|
||
|
||
builder->remove_folder(*folder).apply(*sup);
|
||
bfs::remove_all(root_path);
|
||
REQUIRE(!bfs::exists(root_path));
|
||
|
||
SECTION("success") {
|
||
builder->upsert_folder(folder_id, root_path).apply(*sup);
|
||
CHECK(bfs::exists(root_path));
|
||
auto f = cluster->get_folders().by_id(folder_id);
|
||
REQUIRE(f);
|
||
REQUIRE(!f->is_suspended());
|
||
}
|
||
SECTION("fail & suspend") {
|
||
auto sub_path = root_path / "a" / "dir";
|
||
builder->upsert_folder(folder_id, sub_path).apply(*sup);
|
||
CHECK(!bfs::exists(sub_path));
|
||
|
||
auto f = cluster->get_folders().by_id(folder_id);
|
||
REQUIRE(f);
|
||
REQUIRE(f->is_suspended());
|
||
CHECK(f->get_suspend_reason());
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_no_changes() {
|
||
struct F : fixture_t {
|
||
std::uint32_t get_hash_limit() override { return 2; }
|
||
|
||
void main() noexcept override {
|
||
sys::error_code ec;
|
||
auto &blocks = cluster->get_blocks();
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
auto folder_id = folder->get_id();
|
||
|
||
auto v = proto::Vector();
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, 1);
|
||
proto::set_value(counter, 1);
|
||
|
||
SECTION("single item") {
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"неизменное.bin");
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
SECTION("dir") {
|
||
auto dir = root_path / file_name;
|
||
bfs::create_directories(dir);
|
||
auto modified = to_unix(bfs::last_write_time(dir));
|
||
auto status = bfs::status(dir);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
proto::set_permissions(pr_file, perms);
|
||
proto::set_modified_s(pr_file, modified);
|
||
|
||
builder->local_update(folder_id, pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
CHECK(!file_1->is_local());
|
||
|
||
builder->scan_start(folder_id).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_1.get() == file_2.get());
|
||
CHECK(file_1->is_local());
|
||
}
|
||
SECTION("regular file") {
|
||
auto path = root_path / file_name;
|
||
auto data = std::string("12345");
|
||
write_file(path, data);
|
||
auto modified = to_unix(bfs::last_write_time(path));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_permissions(pr_file, perms);
|
||
proto::set_modified_s(pr_file, modified);
|
||
proto::set_size(pr_file, 5);
|
||
proto::set_block_size(pr_file, 5);
|
||
auto b = proto::BlockInfo();
|
||
proto::set_size(b, 5);
|
||
proto::set_hash(b, utils::sha256_digest(as_bytes(data)).value());
|
||
proto::add_blocks(pr_file, b);
|
||
|
||
builder->local_update(folder_id, pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
REQUIRE(blocks.size() == 1);
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
CHECK(!file_1->is_local());
|
||
|
||
builder->scan_start(folder_id).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_1.get() == file_2.get());
|
||
CHECK(file_1->is_local());
|
||
REQUIRE(blocks.size() == 1);
|
||
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("changes on permissions are ignored") {
|
||
proto::set_permissions(pr_file, perms - 1);
|
||
proto::set_no_permissions(pr_file, true);
|
||
builder->local_update(folder_id, pr_file).apply(*sup);
|
||
auto seq_1 = file_1->get_sequence();
|
||
builder->scan_start(folder_id).apply(*sup);
|
||
bfs::permissions(path, bfs::perms::none);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto seq_2 = file_1->get_sequence();
|
||
CHECK(seq_1 == seq_2);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("symlink") {
|
||
auto file_path = root_path / file_name;
|
||
auto target = bfs::path(L"/куда-то/where");
|
||
bfs::create_symlink(target, file_path);
|
||
auto status = bfs::symlink_status(file_path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_symlink_target(pr_file, narrow(target.wstring()));
|
||
proto::set_type(pr_file, proto::FileInfoType::SYMLINK);
|
||
proto::set_permissions(pr_file, perms);
|
||
|
||
builder->local_update(folder_id, pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
REQUIRE(file_1->is_link());
|
||
REQUIRE(file_1->get_link_target() == narrow(target.wstring()));
|
||
CHECK(!file_1->is_local());
|
||
|
||
builder->scan_start(folder_id).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_1.get() == file_2.get());
|
||
CHECK(file_1->is_local());
|
||
}
|
||
#endif
|
||
}
|
||
SECTION("dir & file") {
|
||
auto file_name_1 = bfs::path(L"b-dir");
|
||
auto dir = root_path / file_name_1;
|
||
bfs::create_directories(dir);
|
||
|
||
auto modified_1 = to_unix(bfs::last_write_time(dir));
|
||
auto perms_1 = static_cast<uint32_t>(bfs::status(dir).permissions());
|
||
|
||
auto pr_file_1 = proto::FileInfo{};
|
||
proto::set_name(pr_file_1, file_name_1.string());
|
||
proto::set_version(pr_file_1, v);
|
||
proto::set_type(pr_file_1, proto::FileInfoType::DIRECTORY);
|
||
proto::set_permissions(pr_file_1, perms_1);
|
||
proto::set_modified_s(pr_file_1, modified_1);
|
||
|
||
auto file_name_2 = bfs::path(L"a-file");
|
||
auto file = root_path / file_name_2;
|
||
write_file(file, "");
|
||
|
||
auto modified_2 = to_unix(bfs::last_write_time(file));
|
||
auto perms_2 = static_cast<uint32_t>(bfs::status(file).permissions());
|
||
|
||
auto pr_file_2 = proto::FileInfo{};
|
||
proto::set_name(pr_file_2, file_name_2.string());
|
||
proto::set_version(pr_file_2, v);
|
||
proto::set_type(pr_file_2, proto::FileInfoType::FILE);
|
||
proto::set_permissions(pr_file_2, perms_2);
|
||
proto::set_modified_s(pr_file_2, modified_2);
|
||
|
||
builder->local_update(folder_id, pr_file_1).local_update(folder_id, pr_file_2).apply(*sup);
|
||
REQUIRE(files->size() == 2);
|
||
|
||
auto sequence = folder_info->get_max_sequence();
|
||
builder->scan_start(folder_id).apply(*sup);
|
||
CHECK(folder_info->get_max_sequence() == sequence);
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_deleted() {
|
||
struct F : fixture_t {
|
||
using paths_t = std::vector<std::string>;
|
||
|
||
void on_diff(const model::diff::advance::local_update_t &diff) noexcept override {
|
||
auto file = folder_info->get_file_infos().by_uuid(diff.uuid);
|
||
auto name = file->get_name()->get_full_name();
|
||
paths.emplace_back(std::string(name));
|
||
}
|
||
void on_diff(const model::diff::local::file_availability_t &diff) noexcept override {
|
||
available.emplace_back(std::string(diff.name));
|
||
}
|
||
|
||
void main() noexcept override {
|
||
sys::error_code ec;
|
||
auto &blocks = cluster->get_blocks();
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
|
||
auto v = proto::Vector();
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, my_short_id);
|
||
proto::set_value(counter, 1);
|
||
|
||
SECTION("single items") {
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"неизменное.bin");
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
SECTION("regular file") {
|
||
proto::set_type(pr_file, proto::FileInfoType::FILE);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("symlink") {
|
||
proto::set_symlink_target(pr_file, "does/not/matter");
|
||
proto::set_type(pr_file, proto::FileInfoType::SYMLINK);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
}
|
||
#endif
|
||
SECTION("regular dir") {
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
}
|
||
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
CHECK(!file_1->is_local());
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_1.get() == file_2.get());
|
||
CHECK(file_1->is_local());
|
||
REQUIRE(file_1->is_deleted());
|
||
CHECK(file_info_t::as_type(file_1->get_type()) == proto::get_type(pr_file));
|
||
}
|
||
SECTION("deleted hierarchy of dirs") {
|
||
auto pr_file = proto::FileInfo{};
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
proto::set_deleted(pr_file, false);
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
auto file_name = bfs::path(L"имя");
|
||
for (auto &name : {"a", "a/bb", "a/cc", "a/bb/ddd"}) {
|
||
proto::set_name(pr_file, name);
|
||
builder->local_update(folder->get_id(), pr_file);
|
||
}
|
||
builder->apply(*sup);
|
||
REQUIRE(files->size() == 4);
|
||
for (auto f : *files) {
|
||
f->mark_local(false);
|
||
}
|
||
|
||
SECTION("whole hierarchy removal") {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 4);
|
||
for (auto f : *files) {
|
||
CHECK(f->is_local());
|
||
CHECK(f->is_deleted());
|
||
}
|
||
}
|
||
SECTION("sub hierarchy removal") {
|
||
bfs::create_directories(root_path / "a" / "cc");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 4);
|
||
|
||
auto dir_a = files->by_name("a");
|
||
REQUIRE(dir_a);
|
||
REQUIRE(dir_a->is_local());
|
||
REQUIRE(!dir_a->is_deleted());
|
||
|
||
auto dir_cc = files->by_name("a/cc");
|
||
REQUIRE(dir_cc);
|
||
REQUIRE(dir_cc->is_local());
|
||
REQUIRE(!dir_cc->is_deleted());
|
||
|
||
auto dir_bb = files->by_name("a/bb");
|
||
REQUIRE(dir_bb);
|
||
REQUIRE(dir_bb->is_local());
|
||
REQUIRE(dir_bb->is_deleted());
|
||
|
||
auto dir_dd = files->by_name("a/bb/ddd");
|
||
REQUIRE(dir_dd);
|
||
REQUIRE(dir_dd->is_local());
|
||
REQUIRE(dir_dd->is_deleted());
|
||
}
|
||
}
|
||
SECTION("order of deletion (1)") {
|
||
auto file_type = GENERATE(0, 1);
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
proto::set_type(pr_file, static_cast<proto::FileInfoType>(file_type));
|
||
proto::set_deleted(pr_file, false);
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
for (auto &name : {"a", "b", "c", "d"}) {
|
||
proto::set_name(pr_file, name);
|
||
builder->local_update(folder->get_id(), pr_file);
|
||
}
|
||
builder->apply(*sup);
|
||
REQUIRE(files->size() == 4);
|
||
for (auto f : *files) {
|
||
f->mark_local(false);
|
||
}
|
||
paths = {};
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto expected = paths_t{
|
||
"a",
|
||
"b",
|
||
"c",
|
||
"d",
|
||
};
|
||
CHECK(paths == expected);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(paths == expected);
|
||
}
|
||
SECTION("order of deletion (2)") {
|
||
auto file_type = GENERATE(0, 1);
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
proto::set_deleted(pr_file, false);
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
for (auto &name : {"a", "a/1", "a/2", "a/3", "b", "b/1", "b/2", "b/3", "a/2/xx", "a/2/yy"}) {
|
||
proto::set_name(pr_file, name);
|
||
builder->local_update(folder->get_id(), pr_file);
|
||
}
|
||
builder->apply(*sup);
|
||
REQUIRE(files->size() == 10);
|
||
for (auto f : *files) {
|
||
f->mark_local(false);
|
||
}
|
||
paths = {};
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
// clang-format off
|
||
auto expected_rm = paths_t{
|
||
"a/1",
|
||
"a/2/xx",
|
||
"a/2/yy",
|
||
"a/2",
|
||
"a/3",
|
||
"a",
|
||
"b/1",
|
||
"b/2",
|
||
"b/3",
|
||
"b",
|
||
};
|
||
// clang-format on
|
||
auto expected_avail = paths_t{
|
||
"a", "a/1", "a/2", "a/2/xx", "a/2/yy", "a/3", "b", "b/1", "b/2", "b/3",
|
||
};
|
||
// clang-format on
|
||
CHECK(paths == expected_rm);
|
||
|
||
available = {};
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(paths == expected_rm);
|
||
CHECK(available == expected_avail);
|
||
}
|
||
}
|
||
|
||
paths_t paths;
|
||
paths_t available;
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_changed() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
sys::error_code ec;
|
||
auto &blocks = cluster->get_blocks();
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
|
||
auto v = proto::Vector();
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, my_short_id);
|
||
proto::set_value(counter, 1);
|
||
|
||
SECTION("change all content of a small file") {
|
||
auto data_1 = as_owned_bytes("12345");
|
||
auto data_2 = as_owned_bytes("67890");
|
||
auto data_x = as_owned_bytes("5432109876");
|
||
|
||
auto hash_1 = utils::sha256_digest(data_1).value();
|
||
auto hash_2 = utils::sha256_digest(data_2).value();
|
||
auto hash_4 = utils::sha256_digest(data_x).value();
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"файлик.bin");
|
||
auto file_path = root_path / file_name;
|
||
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
auto b_1 = proto::BlockInfo();
|
||
proto::set_hash(b_1, hash_1);
|
||
proto::set_size(b_1, data_1.size());
|
||
|
||
auto b_2 = proto::BlockInfo();
|
||
proto::set_hash(b_2, hash_2);
|
||
proto::set_offset(b_2, data_1.size());
|
||
proto::set_size(b_2, data_2.size());
|
||
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::add_blocks(pr_file, b_2);
|
||
proto::set_size(pr_file, data_1.size() + data_2.size());
|
||
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
REQUIRE(blocks.size() == 2);
|
||
|
||
write_file(file_path, "5432109876");
|
||
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
auto seq_1 = file_1->get_sequence();
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
|
||
CHECK(files->size() == 1);
|
||
CHECK(blocks.size() == 1);
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_2->is_local());
|
||
REQUIRE(file_2->iterate_blocks().get_total() == 1);
|
||
CHECK(file_2->iterate_blocks().next()->get_hash() == hash_4);
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
SECTION("file meta changed") {
|
||
auto data_1_str = std::string("12345");
|
||
auto data_1 = as_owned_bytes(data_1_str);
|
||
auto hash_1 = utils::sha256_digest(data_1).value();
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"файлик.bin");
|
||
auto file_path = root_path / file_name;
|
||
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
auto b_1 = proto::BlockInfo();
|
||
proto::set_hash(b_1, hash_1);
|
||
proto::set_size(b_1, data_1.size());
|
||
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::set_size(pr_file, data_1.size());
|
||
|
||
write_file(file_path, data_1_str);
|
||
|
||
auto status = bfs::status(file_path);
|
||
auto modified = to_unix(bfs::last_write_time(file_path));
|
||
auto perms = static_cast<uint32_t>(0444);
|
||
bool perms_changed = false;
|
||
|
||
SECTION("modification time changed") {
|
||
proto::set_permissions(pr_file, perms);
|
||
proto::set_modified_s(pr_file, modified - 1);
|
||
}
|
||
SECTION("permissions changed") {
|
||
proto::set_permissions(pr_file, perms - 1);
|
||
proto::set_modified_s(pr_file, modified);
|
||
perms_changed = true;
|
||
}
|
||
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
REQUIRE(blocks.size() == 1);
|
||
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
auto seq_1 = file_1->get_sequence();
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
|
||
CHECK(files->size() == 1);
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_2->is_local());
|
||
REQUIRE(file_2->iterate_blocks().get_total() == 1);
|
||
CHECK(file_2->iterate_blocks().next()->get_hash() == hash_1);
|
||
auto seq_2 = file_2->get_sequence();
|
||
|
||
if (!perms_changed || utils::platform_t::permissions_supported(file_path)) {
|
||
CHECK(seq_2 > seq_1);
|
||
} else {
|
||
CHECK(seq_2 == seq_1);
|
||
}
|
||
}
|
||
SECTION("dir meta changed") {
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"папка");
|
||
auto file_path = root_path / file_name;
|
||
|
||
bfs::create_directories(file_path);
|
||
|
||
auto status = bfs::status(file_path);
|
||
auto modified = to_unix(bfs::last_write_time(file_path));
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
bool perms_changed = false;
|
||
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
|
||
SECTION("modification time changed") {
|
||
proto::set_permissions(pr_file, perms);
|
||
proto::set_modified_s(pr_file, modified - 1);
|
||
}
|
||
SECTION("permissions changed") {
|
||
proto::set_permissions(pr_file, perms - 1);
|
||
proto::set_modified_s(pr_file, modified);
|
||
perms_changed = true;
|
||
}
|
||
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
auto seq_1 = file_1->get_sequence();
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
|
||
CHECK(files->size() == 1);
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_2->is_local());
|
||
auto seq_2 = file_2->get_sequence();
|
||
|
||
if (perms_changed && utils::platform_t::permissions_supported(file_path)) {
|
||
CHECK(seq_2 > seq_1);
|
||
} else {
|
||
CHECK(seq_2 == seq_1);
|
||
}
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("symlink target changed") {
|
||
auto file_path = root_path / "symlink";
|
||
auto target_1 = std::string_view("/some/where");
|
||
bfs::create_symlink(bfs::path(target_1), file_path, ec);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
auto file_1 = files->by_name(file_path.filename().string());
|
||
REQUIRE(file_1);
|
||
auto seq_1 = file_1->get_sequence();
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
|
||
bfs::remove_all(file_path);
|
||
auto target_2 = std::string_view("/some/where/2");
|
||
bfs::create_symlink(bfs::path(target_2), file_path, ec);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto file_2 = files->by_name(file_path.filename().string());
|
||
REQUIRE(file_2);
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
#endif
|
||
SECTION("append new block") {
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto b1 = std::string(block_sz, '0');
|
||
auto b2 = std::string(block_sz, '1');
|
||
auto b3 = std::string(block_sz, '2');
|
||
|
||
auto data_1 = as_owned_bytes(b1);
|
||
auto data_2 = as_owned_bytes(b2);
|
||
auto data_3 = as_owned_bytes(b3);
|
||
|
||
auto hash_1 = utils::sha256_digest(data_1).value();
|
||
auto hash_2 = utils::sha256_digest(data_2).value();
|
||
auto hash_3 = utils::sha256_digest(data_3).value();
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"файлик.bin");
|
||
auto file_path = root_path / file_name;
|
||
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_version(pr_file, v);
|
||
|
||
auto b_1 = proto::BlockInfo();
|
||
proto::set_hash(b_1, hash_1);
|
||
proto::set_size(b_1, data_1.size());
|
||
|
||
auto b_2 = proto::BlockInfo();
|
||
proto::set_hash(b_2, hash_2);
|
||
proto::set_offset(b_2, data_1.size());
|
||
proto::set_size(b_2, data_2.size());
|
||
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::add_blocks(pr_file, b_2);
|
||
proto::set_size(pr_file, data_1.size() + data_2.size());
|
||
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
REQUIRE(blocks.size() == 2);
|
||
|
||
write_file(file_path, b1 + b2 + b3);
|
||
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
file_1->mark_local(false);
|
||
auto seq_1 = file_1->get_sequence();
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
|
||
CHECK(files->size() == 1);
|
||
CHECK(blocks.size() == 3);
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
CHECK(file_2->is_local());
|
||
REQUIRE(file_2->iterate_blocks().get_total() == 3);
|
||
REQUIRE(file_2->iterate_blocks(2).next()->get_hash() == hash_3);
|
||
REQUIRE(file_2->get_size() == block_sz * 3);
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_type_change() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = bfs::path(L"файлик.bin");
|
||
proto::set_name(pr_file, file_name.string());
|
||
proto::set_sequence(pr_file, 4);
|
||
auto &v = proto::get_version(pr_file);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, my_short_id);
|
||
proto::set_value(counter, 1);
|
||
auto file_path = root_path / file_name;
|
||
|
||
SECTION("has been dir") {
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_1->is_dir());
|
||
auto seq_1 = file_1->get_sequence();
|
||
|
||
SECTION(" -> regular file") {
|
||
write_file(file_path, "");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_2->is_file());
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION(" -> symlink") {
|
||
bfs::create_symlink(bfs::path("/some/where"), file_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_2->is_link());
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
#endif
|
||
}
|
||
SECTION("has been regular file") {
|
||
proto::set_type(pr_file, proto::FileInfoType::FILE);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_1->is_file());
|
||
auto seq_1 = file_1->get_sequence();
|
||
SECTION(" -> dir") {
|
||
bfs::create_directories(file_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_2->is_dir());
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION(" -> symlink") {
|
||
bfs::create_symlink(bfs::path("/some/where"), file_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_2->is_link());
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
#endif
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("has been symlink") {
|
||
proto::set_type(pr_file, proto::FileInfoType::SYMLINK);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
auto file_1 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_1->is_link());
|
||
auto seq_1 = file_1->get_sequence();
|
||
SECTION(" -> dir") {
|
||
bfs::create_directories(file_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_2->is_dir());
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
SECTION(" -> regular file") {
|
||
write_file(file_path, "");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->get_scan_finish() >= folder->get_scan_start());
|
||
auto file_2 = files->by_name(narrow(file_name.wstring()));
|
||
REQUIRE(file_2->is_file());
|
||
auto seq_2 = file_2->get_sequence();
|
||
CHECK(seq_2 > seq_1);
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_resurrection() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
bfs::create_directories(root_path / L"a/b/c");
|
||
write_file(root_path / "a/b/c/file.bin", "12345");
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
auto &files = folder_info->get_file_infos();
|
||
CHECK(files.size() == 4);
|
||
|
||
auto dir_a = files.by_name("a");
|
||
auto dir_b = files.by_name("a/b");
|
||
auto dir_c = files.by_name("a/b/c");
|
||
auto file = files.by_name("a/b/c/file.bin");
|
||
|
||
REQUIRE(dir_a);
|
||
REQUIRE(dir_b);
|
||
REQUIRE(dir_c);
|
||
REQUIRE(file);
|
||
REQUIRE(file->get_size() == 5);
|
||
|
||
CHECK(!dir_a->is_deleted());
|
||
CHECK(!dir_b->is_deleted());
|
||
CHECK(!dir_c->is_deleted());
|
||
CHECK(!file->is_deleted());
|
||
|
||
bfs::remove_all(root_path / L"a");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
CHECK(dir_a->is_deleted());
|
||
CHECK(dir_b->is_deleted());
|
||
CHECK(dir_c->is_deleted());
|
||
CHECK(file->is_deleted());
|
||
|
||
bfs::create_directories(root_path / L"a/b/c");
|
||
write_file(root_path / "a/b/c/file.bin", "12345");
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
CHECK(!dir_a->is_deleted());
|
||
CHECK(!dir_b->is_deleted());
|
||
CHECK(!dir_c->is_deleted());
|
||
CHECK(!file->is_deleted());
|
||
REQUIRE(file->get_size() == 5);
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_partial_scan() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
auto dir_1 = root_path / L"а/б/в";
|
||
auto dir_2 = root_path / L"x/y/z";
|
||
auto file_1 = dir_1 / L"файл-1.bin";
|
||
auto file_2 = dir_2 / L"файл-2.bin";
|
||
|
||
bfs::create_directories(dir_1);
|
||
bfs::create_directories(dir_2);
|
||
write_file(file_1, "12345");
|
||
write_file(file_2, "67890");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
auto &files = folder_info->get_file_infos();
|
||
REQUIRE(files.size() == 6 + 2);
|
||
|
||
auto f_1 = files.by_name(narrow(L"а/б/в/файл-1.bin"));
|
||
auto f_2 = files.by_name(narrow(L"x/y/z/файл-2.bin"));
|
||
|
||
REQUIRE(f_1);
|
||
REQUIRE(f_2);
|
||
|
||
auto seq_1 = f_1->get_sequence();
|
||
auto seq_2 = f_2->get_sequence();
|
||
|
||
write_file(file_1, "1234567890");
|
||
write_file(file_2, "6789012345");
|
||
SECTION("existing dir") {
|
||
auto subdir = GENERATE(L"а", L"а/б", L"а/б/в");
|
||
builder->scan_start(folder->get_id(), narrow(subdir)).apply(*sup);
|
||
CHECK(subdir != std::wstring_view());
|
||
REQUIRE(files.size() == 6 + 2);
|
||
CHECK(f_1->get_sequence() != seq_1);
|
||
CHECK(f_2->get_sequence() == seq_2);
|
||
}
|
||
SECTION("non-existing dir") {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
auto subdir = narrow(GENERATE(L"а", L"а/б", L"а/б/в"));
|
||
bfs::remove_all(root_path / subdir);
|
||
INFO("subdir: " << subdir);
|
||
CHECK(subdir != std::string_view{});
|
||
builder->scan_start(folder->get_id(), narrow(L"а/б/в")).apply(*sup);
|
||
auto dir = files.by_name(narrow(L"а/б/в"));
|
||
REQUIRE(dir);
|
||
auto aug = folder->get_augmentation().get();
|
||
auto folder_entity = static_cast<presentation::folder_entity_t *>(aug);
|
||
auto &a_entity = *folder_entity->get_children().begin();
|
||
auto &b_entity = *a_entity->get_children().begin();
|
||
auto &c_entity = *b_entity->get_children().begin();
|
||
auto presence = c_entity->get_presence(my_device.get());
|
||
auto &entity_stats = c_entity->get_stats();
|
||
auto &presence_stats = presence->get_stats();
|
||
CHECK(entity_stats.entities == 2);
|
||
CHECK(entity_stats.entities == presence_stats.local_entries);
|
||
CHECK(!folder->is_suspended());
|
||
}
|
||
SECTION("resurrection") {
|
||
bfs::remove_all(root_path / L"а");
|
||
auto dir_a = files.by_name(narrow(L"а"));
|
||
auto dir_x = files.by_name("x");
|
||
auto dir_b = files.by_name(narrow(L"а/б"));
|
||
auto dir_c = files.by_name(narrow(L"а/б/в"));
|
||
builder->scan_start(folder->get_id(), narrow(L"а/б")).apply(*sup);
|
||
|
||
REQUIRE(dir_a->is_deleted());
|
||
REQUIRE(dir_b->is_deleted());
|
||
REQUIRE(dir_c->is_deleted());
|
||
CHECK(!dir_x->is_deleted());
|
||
|
||
bfs::create_directories(root_path / L"а/б/в");
|
||
builder->scan_start(folder->get_id(), narrow(L"а/б")).apply(*sup);
|
||
|
||
CHECK(!dir_a->is_deleted());
|
||
CHECK(!dir_b->is_deleted());
|
||
CHECK(!dir_c->is_deleted());
|
||
CHECK(!dir_x->is_deleted());
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_scan_errors() {
|
||
struct F : fixture_t {
|
||
F() {
|
||
processor = [&](fs::fs_slave_t *slave) {
|
||
slave->ec = {};
|
||
sup->log->info("executing foreign task: {}", execute_slave(*slave));
|
||
};
|
||
}
|
||
|
||
std::uint32_t get_hash_limit() override { return 100; }
|
||
|
||
void execute(fs::message::foreign_executor_t &req) noexcept override {
|
||
++exec_attempts;
|
||
if (exec_pool) {
|
||
sup->log->info("processing foreign task (left = {})", exec_pool);
|
||
auto slave = static_cast<fs::fs_slave_t *>(req.payload.get());
|
||
processor(slave);
|
||
--exec_pool;
|
||
} else {
|
||
sup->log->info("ignoring foreign task execution");
|
||
}
|
||
}
|
||
|
||
void main() noexcept override {
|
||
SECTION("root dir errors") {
|
||
SECTION("missing root dir") {
|
||
auto dir_path = root_path / "some-dir";
|
||
folder->set_path(dir_path);
|
||
}
|
||
SECTION("no permissings to read outer dir") {
|
||
auto dir_path = root_path / "some-dir";
|
||
folder->set_path(dir_path);
|
||
bfs::permissions(root_path, bfs::perms::none);
|
||
}
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(folder->is_suspended());
|
||
REQUIRE(folder->get_suspend_reason().message() != "");
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
bfs::permissions(root_path, bfs::perms::all);
|
||
CHECK(exec_attempts == 1);
|
||
}
|
||
SECTION("missing start dir (in model)") {
|
||
builder->scan_start(folder->get_id(), "non-existing-dir").apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(exec_attempts == 0);
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("non-root errors (non-win32)") {
|
||
auto dir_path = root_path / "d1" / "d2";
|
||
auto d1_path = dir_path.parent_path();
|
||
bfs::create_directories(dir_path);
|
||
|
||
auto status = bfs::status(d1_path);
|
||
auto perms = status.permissions();
|
||
bfs::permissions(d1_path, perms, bfs::perm_options::remove);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto ec = sys::error_code{};
|
||
bfs::create_directories(d1_path / L"авось", ec);
|
||
bfs::permissions(d1_path, perms, bfs::perm_options::add);
|
||
|
||
if (ec) {
|
||
REQUIRE(!folder->is_suspended());
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 0);
|
||
} else {
|
||
INFO("Skipping due to unability to prohibit directories creation");
|
||
}
|
||
}
|
||
SECTION("non-sync'able entity (named fifo file)") {
|
||
auto fifo_path = root_path / "fifo";
|
||
auto fifo_str = narrow(fifo_path.wstring());
|
||
REQUIRE(mknod(fifo_str.c_str(), S_IFIFO | 0666, 0) == 0);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 0);
|
||
}
|
||
#endif
|
||
SECTION("generic task error") {
|
||
exec_pool = 1;
|
||
auto dir_path = root_path / "d1" / "d2";
|
||
auto d1_path = dir_path.parent_path();
|
||
bfs::create_directories(dir_path);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 1);
|
||
}
|
||
|
||
SECTION("scan dir errors") {
|
||
int mocked = 0;
|
||
auto generator_type = GENERATE(0, 1);
|
||
auto do_mock = [&](bfs::path dir_path) {
|
||
processor = [&, dir_path = dir_path](fs::fs_slave_t *slave) {
|
||
slave->ec = {};
|
||
bool do_exec = true;
|
||
if (!slave->tasks_in.empty()) {
|
||
auto task = &slave->tasks_in.front();
|
||
auto scan_task = std::get_if<fs::task::scan_dir_t>(task);
|
||
if (scan_task) {
|
||
if (scan_task->path == dir_path) {
|
||
++mocked;
|
||
do_exec = false;
|
||
auto ec = r::make_error_code(r::error_code_t::cancelled);
|
||
sup->log->info("mocking result {}", generator_type);
|
||
if (generator_type == 0) {
|
||
scan_task->ec = {};
|
||
auto info = fs::task::scan_dir_t::child_info_t();
|
||
info.path = dir_path / "xx";
|
||
info.ec = ec;
|
||
scan_task->child_infos.emplace_back(std::move(info));
|
||
} else {
|
||
scan_task->ec = ec;
|
||
}
|
||
slave->tasks_out.emplace_back(std::move(*task));
|
||
slave->tasks_in.pop_front();
|
||
}
|
||
}
|
||
}
|
||
if (do_exec) {
|
||
sup->log->info("executing foreign task: {}", execute_slave(*slave));
|
||
}
|
||
};
|
||
};
|
||
|
||
auto my_short_id = my_device->device_id().get_uint();
|
||
auto dir_path = root_path / "d1";
|
||
auto d1_path = dir_path.parent_path();
|
||
bfs::create_directories(dir_path);
|
||
|
||
auto pr_dir = proto::FileInfo{};
|
||
proto::set_name(pr_dir, "d1");
|
||
proto::set_sequence(pr_dir, 4);
|
||
auto &v = proto::get_version(pr_dir);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, my_short_id);
|
||
proto::set_value(counter, 1);
|
||
builder->local_update(folder->get_id(), pr_dir).apply(*sup);
|
||
CHECK(files->size() == 1);
|
||
auto dir_file = *files->begin();
|
||
|
||
SECTION("new file") {
|
||
do_mock(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(files->size() == 1);
|
||
}
|
||
SECTION("existing file") {
|
||
auto pr_file = proto::FileInfo{};
|
||
proto::set_name(pr_file, "d1/xx");
|
||
proto::set_sequence(pr_file, 5);
|
||
auto &v = proto::get_version(pr_file);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, my_short_id);
|
||
proto::set_value(counter, 1);
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
|
||
do_mock(dir_path);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(files->size() == 2);
|
||
|
||
auto file = files->by_name("d1/xx");
|
||
CHECK(file->is_locally_available());
|
||
}
|
||
|
||
CHECK(dir_file->is_locally_available());
|
||
CHECK(mocked == 1);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
}
|
||
|
||
std::uint32_t exec_pool = 5;
|
||
std::uint32_t exec_attempts = 0;
|
||
task_processor_t processor;
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_read_errors() {
|
||
struct F : fixture_t {
|
||
F() : fixture_t{false} {
|
||
processor = [&](fs::fs_slave_t *slave) {
|
||
slave->ec = {};
|
||
sup->log->info("executing foreign task: {}", execute_slave(*slave));
|
||
};
|
||
}
|
||
|
||
std::uint32_t get_hash_limit() override { return hash_limit; }
|
||
|
||
void execute(fs::message::foreign_executor_t &req) noexcept override {
|
||
if (exec_pool) {
|
||
sup->log->info("processing foreign task (left = {})", exec_pool);
|
||
auto slave = static_cast<fs::fs_slave_t *>(req.payload.get());
|
||
processor(slave);
|
||
--exec_pool;
|
||
} else {
|
||
sup->log->info("ignoring foreign task execution");
|
||
}
|
||
}
|
||
|
||
void main() noexcept override {
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("small unknown/new file") {
|
||
launch_target();
|
||
auto file_path = root_path / "file.bin";
|
||
write_file(file_path, "12345");
|
||
bfs::permissions(file_path, bfs::perms::all, bfs::perm_options::remove);
|
||
|
||
if (read_file(file_path) == "") {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 0);
|
||
}
|
||
}
|
||
SECTION("small known file") {
|
||
launch_target();
|
||
auto file_path = root_path / "file.bin";
|
||
write_file(file_path, "12345");
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto file_1 = *files->begin();
|
||
CHECK(!file_1->is_unreachable());
|
||
CHECK(file_1->is_locally_available());
|
||
|
||
auto max_seq = folder_info->get_max_sequence();
|
||
|
||
bfs::permissions(file_path, bfs::perms::all, bfs::perm_options::remove);
|
||
if (read_file(file_path) == "") {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
REQUIRE(files->size() == 1);
|
||
|
||
CHECK(file_1->is_unreachable());
|
||
CHECK(!file_1->is_locally_available());
|
||
CHECK(folder_info->get_max_sequence() == max_seq);
|
||
}
|
||
}
|
||
#endif
|
||
SECTION("large unknown file") {
|
||
auto file_path = root_path / "file.bin";
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto count = std::uint32_t{5};
|
||
auto b = std::string(block_sz * count, 'x');
|
||
write_file(file_path, b);
|
||
|
||
auto do_mock = [&](std::int32_t block_index) {
|
||
processor = [&, count, block_index](fs::fs_slave_t *slave) {
|
||
slave->ec = {};
|
||
bool do_exec = true;
|
||
if (!slave->tasks_in.empty()) {
|
||
auto task = &slave->tasks_in.front();
|
||
auto iterator = std::get_if<fs::task::segment_iterator_t>(task);
|
||
if (iterator) {
|
||
if (iterator->block_index == block_index) {
|
||
do_exec = false;
|
||
auto ec = r::make_error_code(r::error_code_t::cancelled);
|
||
iterator->ec = ec;
|
||
sup->log->info("mocking result {} block", block_index);
|
||
slave->tasks_out.emplace_back(std::move(*task));
|
||
slave->tasks_in.pop_front();
|
||
}
|
||
}
|
||
}
|
||
if (do_exec) {
|
||
sup->log->info("executing foreign task: {}", execute_slave(*slave));
|
||
}
|
||
};
|
||
};
|
||
|
||
using pair_t = std::tuple<std::int32_t, std::int32_t>;
|
||
// clang-format off
|
||
auto pair = GENERATE(table<std::int32_t, std::int32_t>({
|
||
pair_t{0, 1},
|
||
pair_t{1, 1},
|
||
pair_t{2, 1},
|
||
pair_t{3, 1},
|
||
pair_t{4, 1},
|
||
pair_t{2, 2},
|
||
}));
|
||
// clang-format on
|
||
|
||
std::int32_t block_index;
|
||
std::tie(block_index, hash_limit) = pair;
|
||
|
||
do_mock(block_index);
|
||
launch_target();
|
||
|
||
INFO("block index = " << block_index << ", hash limit = " << hash_limit);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 0);
|
||
CHECK(hasher->digested_blocks <= block_index + hash_limit);
|
||
}
|
||
SECTION("cannot read existing dir") {
|
||
#ifndef SYNCSPIRIT_WIN
|
||
launch_target();
|
||
auto dir_path = root_path / L"папка";
|
||
auto subdir_path = dir_path / L"подпапка";
|
||
auto file_path = subdir_path / "файл.bin";
|
||
bfs::create_directories(subdir_path);
|
||
write_file(file_path, "12345");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 3);
|
||
|
||
auto file_1 = files->by_name(narrow(L"папка"));
|
||
auto file_2 = files->by_name(narrow(L"папка/подпапка"));
|
||
auto file_3 = files->by_name(narrow(L"папка/подпапка/файл.bin"));
|
||
REQUIRE(file_1);
|
||
REQUIRE(file_2);
|
||
REQUIRE(file_3);
|
||
|
||
auto max_seq = folder_info->get_max_sequence();
|
||
|
||
auto perms = bfs::status(dir_path).permissions();
|
||
bfs::permissions(dir_path, bfs::perms::all, bfs::perm_options::remove);
|
||
|
||
auto ec = sys::error_code{};
|
||
bfs::create_directories(dir_path / L"авось", ec);
|
||
if (ec) {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
bfs::permissions(dir_path, perms, bfs::perm_options::replace);
|
||
|
||
CHECK(!folder->is_scanning());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 3);
|
||
CHECK(folder_info->get_max_sequence() == max_seq);
|
||
|
||
CHECK(file_1->is_unreachable());
|
||
CHECK(file_2->is_unreachable());
|
||
CHECK(file_3->is_unreachable());
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 3);
|
||
CHECK(folder_info->get_max_sequence() == max_seq);
|
||
|
||
CHECK(!file_1->is_unreachable());
|
||
CHECK(!file_2->is_unreachable());
|
||
CHECK(!file_3->is_unreachable());
|
||
}
|
||
#endif
|
||
}
|
||
}
|
||
std::uint32_t exec_pool = 10;
|
||
task_processor_t processor;
|
||
std::uint32_t hash_limit = 10;
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_leaks() {
|
||
struct F : fixture_t {
|
||
void launch_hasher() noexcept override {
|
||
hasher = sup->create_actor<managed_hasher_t>().index(1).auto_reply(false).timeout(timeout).finish().get();
|
||
}
|
||
void main() noexcept override {
|
||
write_file(root_path / "file-1.bin", "12345");
|
||
write_file(root_path / "file-2.bin", "12345");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(folder->is_scanning());
|
||
|
||
target->do_shutdown();
|
||
sup->do_process();
|
||
CHECK(files->size() == 0);
|
||
|
||
hasher->process_requests();
|
||
sup->do_process();
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_hashing_fail() {
|
||
struct F : fixture_t {
|
||
std::uint32_t get_hash_limit() override { return 2; }
|
||
void launch_hasher() noexcept override {
|
||
hasher = sup->create_actor<managed_hasher_t>().index(1).subscribe(false).timeout(timeout).finish().get();
|
||
}
|
||
void main() noexcept override {
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto b = std::string(block_sz * 5, 'x');
|
||
write_file(root_path / "file.bin", b);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
sup->do_process();
|
||
CHECK(!folder->get_scan_start().is_special());
|
||
CHECK(!folder->get_scan_finish().is_special());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
CHECK(files->size() == 0);
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_incomplete() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
using clock_t = std::chrono::system_clock;
|
||
auto sha256 = peer_device->device_id().get_sha256();
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto path = root_path / L"файл.syncspirit-tmp";
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = narrow(L"файл");
|
||
proto::set_name(pr_file, file_name);
|
||
proto::set_sequence(pr_file, 4);
|
||
auto &v = proto::get_version(pr_file);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, 55);
|
||
proto::set_value(counter, 2);
|
||
proto::set_modified_s(pr_file, 12345);
|
||
|
||
auto data_1 = as_owned_bytes("12345");
|
||
auto data_2 = as_owned_bytes("67890");
|
||
auto hash_1 = utils::sha256_digest(data_1).value();
|
||
auto hash_2 = utils::sha256_digest(data_2).value();
|
||
auto b_1 = proto::BlockInfo();
|
||
proto::set_hash(b_1, hash_1);
|
||
proto::set_size(b_1, data_1.size());
|
||
auto b_2 = proto::BlockInfo();
|
||
proto::set_hash(b_2, hash_2);
|
||
proto::set_offset(b_2, data_1.size());
|
||
proto::set_size(b_2, data_2.size());
|
||
auto m_time = clock_t::to_time_t(clock_t::now()) - (constants::tmp_min_age * 2);
|
||
|
||
SECTION("no in model => remove") {
|
||
write_file(path, "");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
CHECK(files->size() == 0);
|
||
CHECK(!bfs::exists(path));
|
||
}
|
||
SECTION("exists only in my model => remove") {
|
||
write_file(path, "");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
builder->local_update(folder->get_id(), pr_file)
|
||
.apply(*sup)
|
||
.then()
|
||
.scan_start(folder->get_id())
|
||
.apply(*sup);
|
||
|
||
CHECK(files->size() == 1);
|
||
CHECK(!bfs::exists(path));
|
||
}
|
||
SECTION("found in peer model, remove") {
|
||
auto should_not_exist = true;
|
||
SECTION("size mise mismatch") {
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::set_size(pr_file, data_1.size());
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
write_file(path, "");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
CHECK(files->size() == 0);
|
||
}
|
||
#ifndef SYNCSPIRIT_WIN
|
||
SECTION("cannot read tmp file") {
|
||
write_file(path, "12345");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::set_size(pr_file, data_1.size());
|
||
proto::set_permissions(pr_file, perms);
|
||
proto::set_modified_s(pr_file, to_unix(bfs::last_write_time(path)));
|
||
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
bfs::permissions(path, bfs::perms::all, bfs::perm_options::remove);
|
||
if (read_file(path) == "") {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(files->size() == 0);
|
||
} else {
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
should_not_exist = false;
|
||
}
|
||
}
|
||
#endif
|
||
SECTION("local version is better than remote (local file does exists)") {
|
||
write_file(path, "1234");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto p = root_path / widen(file_name);
|
||
|
||
write_file(p, "12345");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(p);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::set_size(pr_file, data_1.size());
|
||
proto::set_permissions(pr_file, perms);
|
||
proto::set_modified_s(pr_file, to_unix(bfs::last_write_time(p)));
|
||
|
||
builder->local_update(folder->get_id(), pr_file).apply(*sup);
|
||
|
||
REQUIRE(files->size() == 1);
|
||
auto seq_1 = folder_info->get_max_sequence();
|
||
|
||
proto::set_value(counter, 1);
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto seq_2 = folder_info->get_max_sequence();
|
||
REQUIRE(seq_1 == seq_2);
|
||
}
|
||
if (should_not_exist) {
|
||
CHECK(!bfs::exists(path));
|
||
}
|
||
}
|
||
SECTION("2 blocks peer file") {
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::add_blocks(pr_file, b_2);
|
||
proto::set_size(pr_file, data_1.size() + data_2.size());
|
||
|
||
auto model_path = root_path / widen(file_name);
|
||
SECTION("all blocks match => rename & add into model") {
|
||
write_file(path, "1234567890");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_permissions(pr_file, perms);
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
auto max_seq = folder_info->get_max_sequence();
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
CHECK(!bfs::exists(path));
|
||
CHECK(bfs::exists(model_path));
|
||
CHECK(read_file(model_path) == "1234567890");
|
||
CHECK(files->size() == 1);
|
||
auto f = files->by_name(file_name);
|
||
REQUIRE(f);
|
||
CHECK(f->get_size() == 10);
|
||
REQUIRE(f->iterate_blocks().get_total() == 2);
|
||
CHECK(f->iterate_blocks(0).next()->get_hash() == hash_1);
|
||
CHECK(f->iterate_blocks(1).next()->get_hash() == hash_2);
|
||
CHECK(folder_info->get_max_sequence() == max_seq + 1);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
auto f2 = files->by_name(file_name);
|
||
CHECK(f2.get() == f.get());
|
||
REQUIRE(f2->iterate_blocks().get_total() == 2);
|
||
CHECK(folder_info->get_max_sequence() == max_seq + 1);
|
||
}
|
||
SECTION("1st block match") {
|
||
write_file(path, "1234500000");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_permissions(pr_file, perms);
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(bfs::exists(path));
|
||
CHECK(!bfs::exists(model_path));
|
||
CHECK(files->size() == 0);
|
||
|
||
auto peer_file = folder_info_peer->get_file_infos().by_name(file_name);
|
||
CHECK(peer_file->iterate_blocks(0).current().first->local_file());
|
||
CHECK(!peer_file->iterate_blocks(1).current().first->local_file());
|
||
}
|
||
SECTION("2nd block match") {
|
||
write_file(path, "0000067890");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_permissions(pr_file, perms);
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(bfs::exists(path));
|
||
CHECK(!bfs::exists(model_path));
|
||
CHECK(files->size() == 0);
|
||
|
||
auto peer_file = folder_info_peer->get_file_infos().by_name(file_name);
|
||
CHECK(!peer_file->iterate_blocks(0).current().first->local_file());
|
||
CHECK(peer_file->iterate_blocks(1).current().first->local_file());
|
||
}
|
||
SECTION("no block match") {
|
||
write_file(path, "0000000000");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
|
||
proto::set_permissions(pr_file, perms);
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!bfs::exists(path));
|
||
CHECK(!bfs::exists(model_path));
|
||
CHECK(files->size() == 0);
|
||
}
|
||
SECTION("synchronization lock => ignored") {
|
||
write_file(path, "0000000000");
|
||
last_write_time(path, fs::from_unix(m_time));
|
||
auto status = bfs::status(path);
|
||
auto perms = static_cast<uint32_t>(status.permissions());
|
||
proto::set_permissions(pr_file, perms);
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
auto f = folder_info_peer->get_file_infos().by_name(file_name);
|
||
REQUIRE(f);
|
||
f->synchronizing_lock();
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(bfs::exists(path));
|
||
CHECK(!bfs::exists(model_path));
|
||
|
||
f->synchronizing_unlock();
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
CHECK(!bfs::exists(path));
|
||
CHECK(!bfs::exists(model_path));
|
||
CHECK(files->size() == 0);
|
||
}
|
||
}
|
||
|
||
CHECK(!folder->get_scan_start().is_special());
|
||
CHECK(!folder->get_scan_finish().is_special());
|
||
CHECK(folder->get_scan_finish() >= folder->get_scan_start());
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_traversal() {
|
||
struct F : fixture_t {
|
||
using paths_t = std::vector<std::string>;
|
||
|
||
void on_diff(const model::diff::advance::local_update_t &diff) noexcept override {
|
||
auto file = folder_info->get_file_infos().by_uuid(diff.uuid);
|
||
auto name = file->get_name()->get_full_name();
|
||
paths.emplace_back(std::string(name));
|
||
}
|
||
|
||
void main() noexcept override {
|
||
bfs::create_directory(root_path / "a");
|
||
bfs::create_directory(root_path / "a" / "c");
|
||
bfs::create_directory(root_path / "b");
|
||
bfs::create_directory(root_path / "d");
|
||
bfs::create_directory(root_path / "d" / "d1");
|
||
bfs::create_directory(root_path / "d" / "d2");
|
||
write_file(root_path / "x.bin", "");
|
||
write_file(root_path / "y.bin", "");
|
||
write_file(root_path / "a/file.bin", "");
|
||
write_file(root_path / "a/c/file_2.bin", "");
|
||
write_file(root_path / "d/d1/file_3.bin", "");
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(files->size() == 11);
|
||
REQUIRE(paths.size() == 11);
|
||
|
||
// clang-format off
|
||
auto expected = paths_t{
|
||
"a",
|
||
"a/c",
|
||
"a/c/file_2.bin",
|
||
"a/file.bin",
|
||
"b",
|
||
"d",
|
||
"d/d1",
|
||
"d/d1/file_3.bin",
|
||
"d/d2",
|
||
"x.bin",
|
||
"y.bin",
|
||
};
|
||
// clang-format on
|
||
CHECK(paths == expected);
|
||
}
|
||
paths_t paths;
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_importing() {
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
auto peer_short_id = my_device->device_id().get_uint();
|
||
auto modified_s = std::int64_t{12345};
|
||
auto sha256 = peer_device->device_id().get_sha256();
|
||
|
||
SECTION("regular file") {
|
||
auto block_sz = fs::block_sizes[0];
|
||
auto path = root_path / L"файл";
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = narrow(L"файл");
|
||
proto::set_name(pr_file, file_name);
|
||
proto::set_sequence(pr_file, 4);
|
||
auto &v = proto::get_version(pr_file);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, 55);
|
||
proto::set_value(counter, peer_short_id);
|
||
proto::set_modified_s(pr_file, modified_s);
|
||
|
||
auto data_1 = as_owned_bytes("12345");
|
||
auto data_2 = as_owned_bytes("67890");
|
||
auto hash_1 = utils::sha256_digest(data_1).value();
|
||
auto hash_2 = utils::sha256_digest(data_2).value();
|
||
auto b_1 = proto::BlockInfo();
|
||
proto::set_hash(b_1, hash_1);
|
||
proto::set_size(b_1, data_1.size());
|
||
auto b_2 = proto::BlockInfo();
|
||
proto::set_hash(b_2, hash_2);
|
||
proto::set_offset(b_2, data_1.size());
|
||
proto::set_size(b_2, data_2.size());
|
||
SECTION("one block file") {
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::set_size(pr_file, 5);
|
||
proto::set_block_size(pr_file, 5);
|
||
|
||
write_file(path, "12345");
|
||
bfs::last_write_time(path, from_unix(modified_s));
|
||
|
||
proto::set_permissions(pr_file, static_cast<uint32_t>(bfs::status(path).permissions()));
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto f = *files->begin();
|
||
REQUIRE(f);
|
||
CHECK(f->get_size() == proto::get_size(pr_file));
|
||
REQUIRE(f->get_version().as_proto() == v);
|
||
}
|
||
SECTION("two blocks file (non-standard size)") {
|
||
proto::add_blocks(pr_file, b_1);
|
||
proto::add_blocks(pr_file, b_2);
|
||
proto::set_size(pr_file, 10);
|
||
proto::set_block_size(pr_file, 5);
|
||
|
||
write_file(path, "1234567890");
|
||
bfs::last_write_time(path, from_unix(modified_s));
|
||
|
||
proto::set_permissions(pr_file, static_cast<uint32_t>(bfs::status(path).permissions()));
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto f = files->by_name(file_name);
|
||
REQUIRE(f);
|
||
CHECK(f->get_size() == proto::get_size(pr_file));
|
||
REQUIRE(f->get_version().as_proto() == v);
|
||
}
|
||
}
|
||
SECTION("directory") {
|
||
auto path = root_path / L"папка";
|
||
|
||
auto pr_file = proto::FileInfo{};
|
||
auto file_name = narrow(L"папка");
|
||
proto::set_name(pr_file, file_name);
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_type(pr_file, proto::FileInfoType::DIRECTORY);
|
||
auto &v = proto::get_version(pr_file);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, 55);
|
||
proto::set_value(counter, peer_short_id);
|
||
proto::set_modified_s(pr_file, modified_s);
|
||
#ifdef SYNCSPIRIT_WIN
|
||
proto::set_no_permissions(pr_file, true);
|
||
#endif
|
||
|
||
bfs::create_directories(path);
|
||
proto::set_permissions(pr_file, static_cast<uint32_t>(bfs::status(path).permissions()));
|
||
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(files->size() == 1);
|
||
|
||
auto f = files->by_name(file_name);
|
||
REQUIRE(f);
|
||
CHECK(f->get_size() == proto::get_size(pr_file));
|
||
REQUIRE(f->get_version().as_proto() == v);
|
||
}
|
||
SECTION("deleted") {
|
||
SECTION("single deleted file") {
|
||
auto path = root_path / L"файл.bin";
|
||
auto pr_file = proto::FileInfo();
|
||
proto::set_name(pr_file, path.filename().string());
|
||
proto::set_sequence(pr_file, 4);
|
||
proto::set_type(pr_file, proto::FileInfoType::FILE);
|
||
proto::set_deleted(pr_file, true);
|
||
|
||
auto &v = proto::get_version(pr_file);
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, 1);
|
||
proto::set_value(counter, 1);
|
||
|
||
builder->make_index(sha256, folder->get_id()).add(pr_file, peer_device).finish().apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto fi_my = folder->get_folder_infos().by_device(*my_device);
|
||
auto &files_my = fi_my->get_file_infos();
|
||
REQUIRE(files_my.size() == 1);
|
||
auto file = files_my.by_name(path.filename().string());
|
||
REQUIRE(file->get_version().as_proto() == v);
|
||
CHECK(!bfs::exists(path));
|
||
}
|
||
SECTION("deleted file inside deleted dir") {
|
||
auto v = proto::Vector();
|
||
auto &counter = proto::add_counters(v);
|
||
proto::set_id(counter, 1);
|
||
proto::set_value(counter, 1);
|
||
|
||
auto dir_path = bfs::path(L"папка");
|
||
auto file_path = dir_path / L"файл.bin";
|
||
auto narrow_dir = narrow(dir_path.generic_wstring());
|
||
auto narrow_file = narrow(file_path.generic_wstring());
|
||
|
||
auto pr_dir = proto::FileInfo();
|
||
proto::set_name(pr_dir, narrow_dir);
|
||
proto::set_sequence(pr_dir, 4);
|
||
proto::set_type(pr_dir, proto::FileInfoType::DIRECTORY);
|
||
proto::set_deleted(pr_dir, true);
|
||
proto::set_version(pr_dir, v);
|
||
|
||
auto pr_file = proto::FileInfo();
|
||
proto::set_name(pr_file, narrow_file);
|
||
proto::set_sequence(pr_file, 5);
|
||
proto::set_type(pr_file, proto::FileInfoType::FILE);
|
||
proto::set_deleted(pr_file, true);
|
||
proto::set_version(pr_file, v);
|
||
|
||
builder->make_index(sha256, folder->get_id())
|
||
.add(pr_dir, peer_device)
|
||
.add(pr_file, peer_device)
|
||
.finish()
|
||
.apply(*sup);
|
||
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
auto &files_my = folder_info->get_file_infos();
|
||
REQUIRE(files_my.size() == 2);
|
||
|
||
auto file_1 = files_my.by_name(narrow_dir);
|
||
REQUIRE(file_1->get_version().as_proto() == v);
|
||
auto file_2 = files_my.by_name(narrow_file);
|
||
REQUIRE(file_2->get_version().as_proto() == v);
|
||
|
||
auto it = bfs::directory_iterator(root_path);
|
||
auto children_count = std::distance(it, bfs::directory_iterator());
|
||
CHECK(children_count == 0);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_concurrency() {
|
||
static constexpr std::uint_fast32_t N = 5;
|
||
static constexpr auto M = constants::diffs_batch;
|
||
|
||
struct F : fixture_t {
|
||
|
||
void on_model_update(model::message::model_update_t &msg) noexcept override {
|
||
struct V final : model::diff::cluster_visitor_t {
|
||
using parent_t = model::diff::cluster_visitor_t;
|
||
V(int *ptr_) : ptr{ptr_} {}
|
||
|
||
outcome::result<void> operator()(const model::diff::load::interrupt_t &diff,
|
||
void *custom) noexcept override {
|
||
++(*ptr);
|
||
return diff.visit_next(*this, custom);
|
||
}
|
||
|
||
int *ptr;
|
||
};
|
||
|
||
auto v = V(&interrupts);
|
||
std::ignore = msg.payload.diff->visit(v, nullptr);
|
||
}
|
||
|
||
void main() noexcept override {
|
||
for (int i = 0; i < N; ++i) {
|
||
auto letter = static_cast<char>('a' + i);
|
||
auto dir_name = std::string_view(&letter, 1);
|
||
auto dir_path = root_path / "sub-dir" / dir_name;
|
||
bfs::create_directories(dir_path);
|
||
for (std::uint_fast32_t j = 0; j < M; ++j) {
|
||
auto file_name = fmt::format("{:03}.bin", j);
|
||
auto file_path = dir_path / file_name;
|
||
write_file(file_path, "");
|
||
}
|
||
}
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
REQUIRE(files->size() == 1 + N * (M + 1));
|
||
|
||
interrupts = 0;
|
||
bfs::remove_all(root_path / "sub-dir");
|
||
builder->scan_start(folder->get_id()).apply(*sup);
|
||
|
||
CHECK(interrupts >= N);
|
||
CHECK(interrupts <= N * 2);
|
||
}
|
||
|
||
int interrupts = 0;
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
void test_races() {
|
||
static constexpr int N = 5;
|
||
|
||
struct F : fixture_t {
|
||
void main() noexcept override {
|
||
for (int i = 0; i < N; ++i) {
|
||
auto path = root_path / fmt::format("file-{}", i);
|
||
write_file(path, "12345");
|
||
}
|
||
|
||
SECTION("shutdown during I/O") {
|
||
auto diff = builder->scan_start(folder->get_id()).extract();
|
||
sup->send<model::payload::model_update_t>(sup->get_address(), std::move(diff), nullptr);
|
||
target->do_shutdown();
|
||
sup->do_process();
|
||
|
||
CHECK(files->size() == 0);
|
||
CHECK(hasher->digested_blocks == 0);
|
||
}
|
||
}
|
||
};
|
||
F().run();
|
||
}
|
||
|
||
int _init() {
|
||
test::init_logging();
|
||
REGISTER_TEST_CASE(test_simple, "test_simple", "[net]");
|
||
REGISTER_TEST_CASE(test_create_dir, "test_create_dir", "[net]");
|
||
REGISTER_TEST_CASE(test_no_changes, "test_no_changes", "[net]");
|
||
REGISTER_TEST_CASE(test_deleted, "test_deleted", "[net]");
|
||
REGISTER_TEST_CASE(test_changed, "test_changed", "[net]");
|
||
REGISTER_TEST_CASE(test_type_change, "test_type_change", "[net]");
|
||
REGISTER_TEST_CASE(test_resurrection, "test_resurrection", "[net]");
|
||
REGISTER_TEST_CASE(test_partial_scan, "test_partial_scan", "[net]");
|
||
REGISTER_TEST_CASE(test_scan_errors, "test_scan_errors", "[net]");
|
||
REGISTER_TEST_CASE(test_read_errors, "test_read_errors", "[net]");
|
||
REGISTER_TEST_CASE(test_leaks, "test_leaks", "[net]");
|
||
REGISTER_TEST_CASE(test_hashing_fail, "test_hashing_fail", "[net]");
|
||
REGISTER_TEST_CASE(test_incomplete, "test_incomplete", "[net]");
|
||
REGISTER_TEST_CASE(test_traversal, "test_traversal", "[net]");
|
||
REGISTER_TEST_CASE(test_importing, "test_importing", "[net]");
|
||
REGISTER_TEST_CASE(test_concurrency, "test_concurrency", "[net]");
|
||
REGISTER_TEST_CASE(test_races, "test_races", "[net]");
|
||
return 1;
|
||
}
|
||
|
||
static int v = _init();
|