// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2026 Ivan Baidakou #include #include #include #include "model/diff/apply_controller.h" #include "model/diff/diff_assembler.h" #include "model/diff/iterative_controller.h" #include "model/diff/advance/local_update.h" #include "model/diff/cluster_visitor.h" #include "bouncer/messages.hpp" #include "net/names.h" #include "test-utils.h" #include "access.h" #include "diff-builder.h" using namespace syncspirit; using namespace syncspirit::model; using namespace syncspirit::net; using namespace syncspirit::test; namespace r = rotor; namespace rth = r::thread; namespace { namespace resource { r::plugin::resource_id_t interrupt = 0; } // namespace resource } // namespace static const r::pt::time_duration timeout = r::pt::millisec{10}; static constexpr size_t I = 50; static constexpr size_t N = 2500; struct my_local_update_t : diff::advance::local_update_t { using parent_t = diff::advance::local_update_t; using parent_t::parent_t; outcome::result apply_impl(diff::apply_controller_t &controller, void *custom) const noexcept override { char buff[1024 * 100] = {0}; return parent_t::apply_impl(controller, custom); } }; struct my_diff_builder_t : diff_builder_t { using parent_t = diff_builder_t; using parent_t::parent_t; diff_builder_t &local_update(std::string_view folder_id, const proto::FileInfo &file_) noexcept override { return assign(new my_local_update_t(*cluster, *sequencer, file_, folder_id)); } }; template using sample_supervisor_base_t = model::diff::iterative_controller_t; struct sample_supervisor_t : sample_supervisor_base_t { using parent_t = sample_supervisor_base_t; using config_t = typename parent_t::config_t; using parent_t::cluster; sample_supervisor_t(config_t &cfg) : parent_t(this, resource::interrupt, cfg) { log = utils::get_logger("sample-sup"); } void configure(r::plugin::plugin_base_t &plugin) noexcept override { parent_t::configure(plugin); plugin.with_casted([&](auto &p) { p.set_identity("sample-sup", false); bouncer = address; }); plugin.with_casted( [&](auto &p) { p.register_name(names::coordinator, get_address()); }); plugin.with_casted([&](auto &p) { p.subscribe_actor(&sample_supervisor_t::on_model_interrupt); p.subscribe_actor(&sample_supervisor_t::on_model_update); p.subscribe_actor(&sample_supervisor_t::on_package); }); } void on_package(bouncer::message::package_t &msg) noexcept { LOG_TRACE(log, "on_package"); put(std::move(msg.payload)); ++bounces; } std::uint32_t bounces = 0; }; struct fixture_t { void run() { auto local_id_str = "KHQNO2S-5QSILRK-YX4JZZ4-7L77APM-QNVGZJT-EKU7IFI-PNEPBMY-4MXFMQD"; auto local_id = device_id_t::from_string(local_id_str).value(); auto local_device = device_t::create(local_id, "local-device").value(); cluster = model::cluster_ptr_t(new cluster_t(local_device, 1)); cluster->get_devices().put(local_device); auto ctx = rth::system_context_ptr_t(new rth::system_context_thread_t()); sup = create_sup(ctx.get()); sup->cluster = cluster; sup->start(); sup->do_process(); REQUIRE(static_cast(sup)->access() == r::state_t::OPERATIONAL); auto builder = my_diff_builder_t(*cluster, sup->get_address()); folder_id = "1234-5678"; builder.upsert_folder(folder_id, "").apply(*sup); auto &local_folder = *cluster->get_folders().by_id(folder_id); local_files = &local_folder.get_folder_infos().by_device(*local_device)->get_file_infos(); main(builder); sup->do_shutdown(); sup->do_process(); REQUIRE(static_cast(sup)->access() == r::state_t::SHUT_DOWN); } virtual sample_supervisor_t *create_sup(r::system_context_t *ctx) noexcept { return ctx->create_supervisor().timeout(timeout).create_registry().finish().get(); } virtual void main(diff_builder_t &builder) noexcept = 0; sample_supervisor_t *sup; cluster_ptr_t cluster; std::string_view folder_id; file_infos_map_t *local_files; }; void test_large_diffs_chain() { struct F : fixture_t { using fixture_t::fixture_t; void main(diff_builder_t &builder) noexcept override { auto pr_file = proto::FileInfo(); proto::set_name(pr_file, "root"); proto::set_type(pr_file, proto::FileInfoType::DIRECTORY); SECTION("via builder") { builder.local_update(folder_id, pr_file).apply(*sup); for (size_t i = 0; i < N; ++i) { auto name = fmt::format("subdir-{}", i); proto::set_name(pr_file, name); builder.local_update(folder_id, pr_file); if (i % I == 0) { builder.interrupt(); } } builder.apply(*sup); } SECTION("via assembler") { auto assember = model::diff::diff_assember_t(I); assember.push_back(builder.local_update(folder_id, pr_file).extract().get()); for (size_t i = 0; i < N; ++i) { auto name = fmt::format("subdir-{}", i); proto::set_name(pr_file, name); auto diff = builder.local_update(folder_id, pr_file).extract(); assember.push_back(diff.get()); } sup->send(sup->get_address(), assember.consume(), nullptr); sup->do_process(); } CHECK(local_files->size() == N + 1); CHECK(sup->bounces == N / I); } }; F().run(); } void test_updates_intermixture() { static constexpr size_t I = 2; static constexpr size_t N = 10; struct sup_t : sample_supervisor_t { using parent_t = sample_supervisor_t; using parent_t::parent_t; void process(diff::cluster_diff_t &diff, model::payload::apply_context_t &context) noexcept override { parent_t::process(diff, context); auto r = diff.visit(*this, {}); CHECK(r); } outcome::result operator()(const diff::advance::local_update_t &diff, void *custom) noexcept override { ++update_visits; return parent_t::operator()(diff, custom); } int update_visits = 0; }; struct F : fixture_t { using fixture_t::fixture_t; sample_supervisor_t *create_sup(r::system_context_t *ctx) noexcept override { return ctx->create_supervisor().timeout(timeout).create_registry().finish().get(); } void main(diff_builder_t &builder) noexcept override { auto pr_file = proto::FileInfo(); proto::set_name(pr_file, "root"); proto::set_type(pr_file, proto::FileInfoType::DIRECTORY); builder.local_update(folder_id, pr_file); for (size_t i = 0; i < N; ++i) { auto name = fmt::format("subdir-{}", i); proto::set_name(pr_file, name); builder.local_update(folder_id, pr_file); if (i % I == 0) { builder.interrupt(); } } builder.send(*sup); proto::set_name(pr_file, "n1"); builder.local_update(folder_id, pr_file).send(*sup); proto::set_name(pr_file, "n2"); builder.local_update(folder_id, pr_file).apply(*sup); CHECK(local_files->size() == N + 3); CHECK(sup->bounces == N / I); auto impl = static_cast(sup); CHECK(impl->update_visits == local_files->size()); } }; F().run(); } int _init() { test::init_logging(); REGISTER_TEST_CASE(test_large_diffs_chain, "test_large_diffs_chain", "[model]"); REGISTER_TEST_CASE(test_updates_intermixture, "test_updates_intermixture", "[model]"); return 1; } static int v = _init();