#include #include #include #include #include #include #include #include #include using namespace structive; #define REQUIRE(expression) do { if (!(expression)) { std::fprintf(stderr, "REQUIRE failed: %s:%d: %s\n", __FILE__, __LINE__, #expression); std::abort(); } } while (false) struct Sync_Device : Property_Object { Sync_Device() = default; explicit Sync_Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {} int id{1}; int a{10}; int b{20}; int c{30}; }; template <> struct structive::Type_Descriptor { static auto get() { return object( synchronization(sync_all_independent), field<&Sync_Device::id>(key<"id">, read_only), field<&Sync_Device::a>(key<"a">), field<&Sync_Device::b>(key<"b">), field<&Sync_Device::c>(key<"c">) ); } }; static Sync_Device with_plan(Synchronization_Plan plan) { return Sync_Device{property_synchronization(std::move(plan))}; } static void test_topology_resolution() { Sync_Device independent; auto ro = independent.lock_slot<&Sync_Device::id>(); auto a = independent.lock_slot<&Sync_Device::a>(); auto b = independent.lock_slot<&Sync_Device::b>(); auto c = independent.lock_slot<&Sync_Device::c>(); REQUIRE(ro == Resolved_Synchronization_View::unsynchronized_slot); REQUIRE(independent.resolved_synchronization().lock_count == 3); REQUIRE(a != b && a != c && b != c); auto shared = with_plan(synchronization(sync_all_shared)); REQUIRE(shared.resolved_synchronization().lock_count == 1); REQUIRE(shared.lock_slot<&Sync_Device::a>() == shared.lock_slot<&Sync_Device::b>()); REQUIRE(shared.lock_slot<&Sync_Device::b>() == shared.lock_slot<&Sync_Device::c>()); REQUIRE(shared.lock_slot<&Sync_Device::id>() == Resolved_Synchronization_View::unsynchronized_slot); auto unsynchronized = with_plan(synchronization(sync_all_unsynchronized)); REQUIRE(unsynchronized.resolved_synchronization().lock_count == 0); REQUIRE(unsynchronized.lock_slot<&Sync_Device::a>() == Resolved_Synchronization_View::unsynchronized_slot); REQUIRE(unsynchronized.lock_slot<&Sync_Device::b>() == Resolved_Synchronization_View::unsynchronized_slot); REQUIRE(unsynchronized.lock_slot<&Sync_Device::c>() == Resolved_Synchronization_View::unsynchronized_slot); auto grouped = Sync_Device{property_synchronization(synchronization(sync_all_independent, sync_group<&Sync_Device::a, &Sync_Device::b>("ab")))}; REQUIRE(grouped.resolved_synchronization().lock_count == 2); REQUIRE(grouped.lock_slot<&Sync_Device::a>() == grouped.lock_slot<&Sync_Device::b>()); REQUIRE(grouped.lock_slot<&Sync_Device::a>() != grouped.lock_slot<&Sync_Device::c>()); auto one_unsynchronized = Sync_Device{property_synchronization(synchronization(sync_all_independent, sync_unsynchronized<&Sync_Device::b>()))}; REQUIRE(one_unsynchronized.resolved_synchronization().lock_count == 2); REQUIRE(one_unsynchronized.lock_slot<&Sync_Device::b>() == Resolved_Synchronization_View::unsynchronized_slot); auto one_independent = Sync_Device{property_synchronization(synchronization(sync_all_shared, sync_independent<&Sync_Device::c>()))}; REQUIRE(one_independent.resolved_synchronization().lock_count == 2); REQUIRE(one_independent.lock_slot<&Sync_Device::a>() == one_independent.lock_slot<&Sync_Device::b>()); REQUIRE(one_independent.lock_slot<&Sync_Device::c>() != one_independent.lock_slot<&Sync_Device::a>()); auto read_only_grouped = Sync_Device{property_synchronization(synchronization(sync_all_independent, sync_group<&Sync_Device::id, &Sync_Device::a>("mixed")))}; REQUIRE(read_only_grouped.lock_slot<&Sync_Device::id>() == Resolved_Synchronization_View::unsynchronized_slot); REQUIRE(read_only_grouped.resolved_synchronization().lock_count == 3); } static void test_plan_validation() { bool unknown_thrown = false; try { Synchronization_Plan plan; plan.independent("missing"); auto device = with_plan(std::move(plan)); (void)device; } catch (const std::invalid_argument&) { unknown_thrown = true; } REQUIRE(unknown_thrown); bool duplicate_property_thrown = false; try { Synchronization_Plan plan; plan.independent("a").unsynchronized("a"); auto device = with_plan(std::move(plan)); (void)device; } catch (const std::invalid_argument&) { duplicate_property_thrown = true; } REQUIRE(duplicate_property_thrown); bool empty_group_thrown = false; try { Synchronization_Plan plan; plan.group("empty", std::span{}); auto device = with_plan(std::move(plan)); (void)device; } catch (const std::invalid_argument&) { empty_group_thrown = true; } REQUIRE(empty_group_thrown); } static void test_dynamic_guard_validation() { Sync_Device device; bool unknown_thrown = false; try { auto guard = device.lock_shared({"missing"}); (void)guard; } catch (const std::invalid_argument&) { unknown_thrown = true; } REQUIRE(unknown_thrown); bool read_only_unique_thrown = false; try { auto guard = device.lock_unique({"id"}); (void)guard; } catch (const std::invalid_argument&) { read_only_unique_thrown = true; } REQUIRE(read_only_unique_thrown); auto read_guard = device.lock_shared({"id"}); REQUIRE(read_guard.get<"id">() == 1); } static void test_independent_and_shared_blocking() { Sync_Device independent; std::binary_semaphore independent_other_done{0}; { auto guard = independent.lock_unique<&Sync_Device::a>(); std::jthread writer([&] { independent.write<&Sync_Device::b>(21); independent_other_done.release(); }); REQUIRE(independent_other_done.try_acquire_for(std::chrono::milliseconds(200))); } REQUIRE(independent.read<&Sync_Device::b>() == 21); std::binary_semaphore same_done{0}; std::jthread same_writer; { auto guard = independent.lock_unique<&Sync_Device::a>(); same_writer = std::jthread([&] { independent.write<&Sync_Device::a>(11); same_done.release(); }); REQUIRE(!same_done.try_acquire_for(std::chrono::milliseconds(20))); } REQUIRE(same_done.try_acquire_for(std::chrono::seconds(2))); same_writer.join(); auto shared = with_plan(synchronization(sync_all_shared)); std::binary_semaphore shared_done{0}; std::jthread shared_writer; { auto guard = shared.lock_unique<&Sync_Device::a>(); shared_writer = std::jthread([&] { shared.write<&Sync_Device::b>(22); shared_done.release(); }); REQUIRE(!shared_done.try_acquire_for(std::chrono::milliseconds(20))); } REQUIRE(shared_done.try_acquire_for(std::chrono::seconds(2))); shared_writer.join(); } static void test_static_guard_order_is_stable() { Sync_Device device; std::barrier start{2}; std::jthread first([&] { start.arrive_and_wait(); for (int index = 0; index < 100; ++index) { auto guard = device.lock_unique<&Sync_Device::a, &Sync_Device::b>(); auto a = guard.get<"a">(); auto b = guard.get<&Sync_Device::b>(); guard.set<"a">(a + 1); guard.set<&Sync_Device::b>(b + 1); } }); std::jthread second([&] { start.arrive_and_wait(); for (int index = 0; index < 100; ++index) { auto guard = device.lock_unique<&Sync_Device::b, &Sync_Device::a>(); auto b = guard.get<"b">(); auto a = guard.get<&Sync_Device::a>(); guard.set<"b">(b + 1); guard.set<&Sync_Device::a>(a + 1); } }); first.join(); second.join(); REQUIRE(device.read<&Sync_Device::a>() == 210); REQUIRE(device.read<&Sync_Device::b>() == 220); } int main() { test_topology_resolution(); test_plan_validation(); test_dynamic_guard_validation(); test_independent_and_shared_blocking(); test_static_guard_order_is_stable(); }