#include "adminive/adapters/nlohmann_json.hpp" #include "adminive/http.hpp" #include #include "adminive_test.hpp" #include #include namespace managed_test { using Json = nlohmann::json; struct Section_Config { int editable_value{1}; int immutable_value{2}; int backend_value{3}; int unsynchronized_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 std::atomic exclusive_locks{}; static inline std::atomic shared_locks{}; void lock() noexcept { exclusive_locks.fetch_add(1, std::memory_order_relaxed); } void unlock() noexcept {} void lock_shared() noexcept { shared_locks.fetch_add(1, std::memory_order_relaxed); } void unlock_shared() noexcept {} static void reset() noexcept { exclusive_locks.store(0, std::memory_order_relaxed); shared_locks.store(0, std::memory_order_relaxed); } }; template concept Writable_Field = requires(Field field) { field.write([](auto& value) { ++value; }); }; template concept Reference_Escaping_Read = requires(Field field) { field.read([](const auto& value) -> const auto& { return value; }); }; } namespace adminive { template <> struct Type_Descriptor { static auto get() { using T = managed_test::Section_Config; return object("section", "Section", ADMINIVE_FIELD(T, editable_value).editable(), ADMINIVE_FIELD(T, immutable_value), ADMINIVE_FIELD(T, backend_value).read_write(), ADMINIVE_FIELD(T, unsynchronized_value).editable().unsynchronized()); } }; template <> struct Type_Descriptor { static auto get() { using T = managed_test::Root_Config; return object("root", "Root", ADMINIVE_FIELD(T, section), ADMINIVE_FIELD(T, immutable_value)); } }; template <> struct Type_Descriptor { static auto get() { using T = managed_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 managed_test; using Guarded = adminive::Managed_Value>; Guarded guarded; auto immutable = guarded.member<&Section_Config::immutable_value>(); auto editable = guarded.member<&Section_Config::editable_value>(); auto backend = guarded.member<&Section_Config::backend_value>(); auto unsynchronized = guarded.member<&Section_Config::unsynchronized_value>(); static_assert(!Writable_Field); static_assert(Writable_Field); static_assert(Writable_Field); static_assert(Writable_Field); static_assert(!Reference_Escaping_Read); const auto resolved = guarded.resolved_synchronization(); ADMINIVE_CHECK(resolved.lock_count == 2); ADMINIVE_CHECK(resolved.slot(0) != structive::Resolved_Synchronization_View::unsynchronized_slot); ADMINIVE_CHECK(resolved.slot(1) == structive::Resolved_Synchronization_View::unsynchronized_slot); ADMINIVE_CHECK(resolved.slot(2) != structive::Resolved_Synchronization_View::unsynchronized_slot); ADMINIVE_CHECK(resolved.slot(0) != resolved.slot(2)); ADMINIVE_CHECK(resolved.slot(3) == structive::Resolved_Synchronization_View::unsynchronized_slot); Probe_Lock::reset(); ADMINIVE_CHECK(immutable.snapshot() == 2); ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); Probe_Lock::reset(); editable.write([](int& value) { value = 11; }); ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1); ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); Probe_Lock::reset(); backend.write([](int& value) { value = 12; }); ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1); Probe_Lock::reset(); unsynchronized.write([](int& value) { value = 13; }); ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); const auto descriptor = adminive::to_descriptor_json(); ADMINIVE_CHECK(descriptor.at("protocol_version") == 5); ADMINIVE_CHECK(!descriptor.at("fields").at(0).contains("lock_mode")); ADMINIVE_CHECK(descriptor.at("fields").at(0).at("writable") == true); ADMINIVE_CHECK(descriptor.at("fields").at(0).at("synchronized") == true); ADMINIVE_CHECK(descriptor.at("fields").at(1).at("writable") == false); ADMINIVE_CHECK(descriptor.at("fields").at(1).at("synchronized") == false); ADMINIVE_CHECK(descriptor.at("fields").at(2).at("writable") == true); ADMINIVE_CHECK(descriptor.at("fields").at(2).at("editable") == false); ADMINIVE_CHECK(descriptor.at("fields").at(2).at("synchronized") == true); ADMINIVE_CHECK(descriptor.at("fields").at(3).at("writable") == true); ADMINIVE_CHECK(descriptor.at("fields").at(3).at("synchronized") == false); adminive::Managed_Value> nested; auto section = nested.member<&Root_Config::section>(); static_assert(!Writable_Field); Probe_Lock::reset(); ADMINIVE_CHECK(section.member<&Section_Config::immutable_value>().snapshot() == 2); ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); Probe_Lock::reset(); section.member<&Section_Config::editable_value>().write([](int& value) { value = 31; }); ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 1); Probe_Lock::reset(); section.member<&Section_Config::unsynchronized_value>().write([](int& value) { value = 32; }); ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); adminive::Managed_Value no_lock; adminive::Resource_Service service(no_lock, "/section"); const auto rejected = service.update_response(R"({"backend_value":99,"immutable_value":88})"); ADMINIVE_CHECK(rejected.status == 422); const auto update = service.update_response(R"({"editable_value":21,"unsynchronized_value":22})"); ADMINIVE_CHECK(update.status == 200); const auto no_lock_snapshot = no_lock.snapshot(); ADMINIVE_CHECK(no_lock_snapshot.editable_value == 21); ADMINIVE_CHECK(no_lock_snapshot.backend_value == 3); ADMINIVE_CHECK(no_lock_snapshot.immutable_value == 2); ADMINIVE_CHECK(no_lock_snapshot.unsynchronized_value == 22); adminive::Managed_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); ADMINIVE_CHECK(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(); ADMINIVE_CHECK(concurrent_snapshot.first == 4999); ADMINIVE_CHECK(concurrent_snapshot.second == 4999); ADMINIVE_CHECK(concurrent_snapshot.immutable_value == 7); return 0; }