195 lines
8.3 KiB
C++
195 lines
8.3 KiB
C++
#include <structive/property/property.hpp>
|
|
#include <barrier>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <semaphore>
|
|
#include <stdexcept>
|
|
#include <string_view>
|
|
#include <thread>
|
|
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> {
|
|
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))};
|
|
}
|
|
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() {
|
|
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<const std::string_view>{});
|
|
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();
|
|
}
|