Files
Structive/core/tests/synchronization_test.cpp
T
2026-08-09 22:47:13 +08:00

162 lines
7.5 KiB
C++

#include <structive/property/property.hpp>
#include "test.hpp"
#include <barrier>
#include <chrono>
#include <semaphore>
#include <stdexcept>
#include <string_view>
#include <thread>
using namespace structive;
struct Sync_Device : Property_Object<Sync_Device> {
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<Sync_Device> {
static auto get() {
return object<Sync_Device>(
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))};
}
template <class Configure>
static void require_invalid_plan(Configure configure) {
Synchronization_Plan plan;
configure(plan);
STRUCTIVE_CHECK_THROWS_AS(with_plan(std::move(plan)), std::invalid_argument);
}
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<Sync_Device>(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<Sync_Device>(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<Sync_Device>(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<Sync_Device>(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() {
require_invalid_plan([](auto& plan) { plan.independent("missing"); });
require_invalid_plan([](auto& plan) { plan.independent("a").unsynchronized("a"); });
require_invalid_plan([](auto& plan) { plan.group("empty", {}); });
require_invalid_plan([](auto& plan) { plan.group("same", {"a"}).group("same", {"b"}); });
}
static void test_dynamic_guard_validation() {
Sync_Device device;
STRUCTIVE_CHECK_THROWS_AS(device.lock_shared({"missing"}), std::invalid_argument);
STRUCTIVE_CHECK_THROWS_AS(device.lock_unique({"id"}), std::invalid_argument);
auto read_guard = device.lock_shared({"id"});
REQUIRE(read_guard.get<"id">() == 1);
STRUCTIVE_CHECK_THROWS_AS(read_guard.get<"a">(), std::logic_error);
auto write_guard = device.lock_unique({"a"});
STRUCTIVE_CHECK_THROWS_AS(write_guard.set<"b">(21), std::logic_error);
}
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();
}