#include "adminive/adapters/nlohmann_json.hpp" #include "adminive/http.hpp" #include #include #include #include #include #include namespace synchronized_test { using Json = nlohmann::json; struct Section_Config { int editable_value{1}; int immutable_value{2}; int forced_value{3}; int unlocked_editable_value{4}; }; struct Root_Config { Section_Config section; int immutable_value{5}; }; struct Concurrent_Config { int first{}; int second{}; int immutable_value{7}; }; struct Probe_Lock { static inline int exclusive_locks{}; static inline int shared_locks{}; void lock() noexcept { ++exclusive_locks; } void unlock() noexcept {} void lock_shared() noexcept { ++shared_locks; } void unlock_shared() noexcept {} static void reset() noexcept { exclusive_locks = 0; shared_locks = 0; } }; } namespace adminive { template <> struct Type_Descriptor { static auto get() { using T = synchronized_test::Section_Config; return object("section", "Section", ADMINIVE_FIELD(T, editable_value).editable(), ADMINIVE_FIELD(T, immutable_value), ADMINIVE_FIELD(T, forced_value).locked(), ADMINIVE_FIELD(T, unlocked_editable_value).editable().locked(false)); } }; template <> struct Type_Descriptor { static auto get() { using T = synchronized_test::Root_Config; return object("root", "Root", ADMINIVE_FIELD(T, section).locked(false), ADMINIVE_FIELD(T, immutable_value)); } }; template <> struct Type_Descriptor { static auto get() { using T = synchronized_test::Concurrent_Config; return object("concurrent", "Concurrent", ADMINIVE_FIELD(T, first).editable(), ADMINIVE_FIELD(T, second).editable(), ADMINIVE_FIELD(T, immutable_value)); } }; } int main() { using namespace synchronized_test; static_assert(!adminive::Synchronized_Result); static_assert(!adminive::Synchronized_Result>); adminive::Synchronized_Value guarded; Probe_Lock::reset(); const auto immutable_root = guarded.member<&Root_Config::immutable_value>().read([](const int& value) { return value; }); assert(immutable_root == 5); assert(Probe_Lock::exclusive_locks == 0); assert(Probe_Lock::shared_locks == 1); auto section = guarded.member<&Root_Config::section>(); Probe_Lock::reset(); const auto immutable_child = section.member<&Section_Config::immutable_value>().read([](const int& value) { return value; }); assert(immutable_child == 2); assert(Probe_Lock::exclusive_locks == 0); assert(Probe_Lock::shared_locks == 1); Probe_Lock::reset(); section.member<&Section_Config::editable_value>().write([](int& value) { value = 7; }); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks >= 2); Probe_Lock::reset(); section.member<&Section_Config::forced_value>().write([](int& value) { value = 8; }); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks >= 2); Probe_Lock::reset(); section.member<&Section_Config::unlocked_editable_value>().write([](int& value) { value = 9; }); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks == 0); adminive::Resource_Service section_service(section, "/section"); Probe_Lock::reset(); const auto section_response = section_service.update_response(R"({"editable_value":10})"); assert(section_response.status == 200); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks == 1); adminive::Resource_Service root_scope_service(section, "/section-root", {}, adminive::Resource_Lock_Scope::root); Probe_Lock::reset(); const auto root_scope_response = root_scope_service.update_response(R"({"editable_value":11})"); assert(root_scope_response.status == 200); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks == 0); Probe_Lock::reset(); const Json section_json = adminive::to_json(section); assert(section_json.at("editable_value") == 11); assert(section_json.at("forced_value") == 8); assert(section_json.at("unlocked_editable_value") == 9); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks == 1); Probe_Lock::reset(); const Json root_json = adminive::to_json(guarded); assert(root_json.at("section").at("editable_value") == 11); assert(Probe_Lock::exclusive_locks == 1); assert(Probe_Lock::shared_locks == 0); const auto root_descriptor = adminive::to_descriptor_json(); assert(root_descriptor.at("fields").at(0).at("lock_mode") == "disabled"); assert(root_descriptor.at("fields").at(0).at("synchronized") == true); const auto descriptor = adminive::to_descriptor_json(); assert(descriptor.at("protocol_version") == 3); assert(descriptor.at("fields").at(0).at("synchronized") == true); assert(descriptor.at("fields").at(1).at("synchronized") == false); assert(descriptor.at("fields").at(2).at("lock_mode") == "enabled"); assert(descriptor.at("fields").at(3).at("lock_mode") == "disabled"); adminive::Synchronized_Value unlocked; adminive::Resource_Service unlocked_service(unlocked, "/unlocked"); const auto response = unlocked_service.update_response(R"({"editable_value":11})"); assert(response.status == 200); assert(unlocked.snapshot().editable_value == 11); adminive::Synchronized_Value concurrent; std::atomic start{}; auto first_writer = std::thread([&] { while(!start.load(std::memory_order_acquire)) {} for(int index = 0; index < 5000; ++index) { concurrent.member<&Concurrent_Config::first>().write([index](int& value) { value = index; }); } }); auto second_writer = std::thread([&] { while(!start.load(std::memory_order_acquire)) {} for(int index = 0; index < 5000; ++index) { concurrent.member<&Concurrent_Config::second>().write([index](int& value) { value = index; }); } }); auto serializer = std::thread([&] { while(!start.load(std::memory_order_acquire)) {} for(int index = 0; index < 1000; ++index) { const Json value = adminive::to_json(concurrent); assert(value.at("immutable_value") == 7); } }); start.store(true, std::memory_order_release); first_writer.join(); second_writer.join(); serializer.join(); const auto concurrent_snapshot = concurrent.snapshot(); assert(concurrent_snapshot.first == 4999); assert(concurrent_snapshot.second == 4999); return 0; }