163 lines
6.4 KiB
C++
163 lines
6.4 KiB
C++
#include <structive/property/property.hpp>
|
|
#include "test.hpp"
|
|
#include <chrono>
|
|
#include <semaphore>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <typeinfo>
|
|
using namespace structive;
|
|
struct Runtime_Device : Property_Object<Runtime_Device> {
|
|
int value{10};
|
|
int serial{7};
|
|
int command{0};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Runtime_Device> {
|
|
static auto get() {
|
|
return object<Runtime_Device>(
|
|
synchronization(sync_all_independent),
|
|
field<&Runtime_Device::value>(key<"value">),
|
|
field<&Runtime_Device::serial>(key<"serial">, read_only),
|
|
field<&Runtime_Device::command>(key<"command">, write_only)
|
|
);
|
|
}
|
|
};
|
|
struct Move_Only_Runtime_Device : Property_Object<Move_Only_Runtime_Device> {
|
|
std::unique_ptr<int> value{std::make_unique<int>(1)};
|
|
};
|
|
template <>
|
|
struct structive::Type_Descriptor<Move_Only_Runtime_Device> {
|
|
static auto get() {
|
|
return object<Move_Only_Runtime_Device>(field<&Move_Only_Runtime_Device::value>(key<"value">));
|
|
}
|
|
};
|
|
struct Runtime_Read_Capture {
|
|
std::size_t calls{};
|
|
std::size_t index{};
|
|
std::string key;
|
|
const std::type_info* type{};
|
|
int value{};
|
|
};
|
|
static void capture_int(void* context, std::size_t index, std::string_view key, const std::type_info& type, const void* value) {
|
|
auto& capture = *static_cast<Runtime_Read_Capture*>(context);
|
|
++capture.calls;
|
|
capture.index = index;
|
|
capture.key = key;
|
|
capture.type = &type;
|
|
capture.value = *static_cast<const int*>(value);
|
|
}
|
|
template <class Object>
|
|
concept Can_Read_Command = requires(const Object& object) {
|
|
object.template read<"command">();
|
|
};
|
|
template <class Object>
|
|
concept Can_Write_Serial = requires(Object& object) {
|
|
object.template write<"serial">(1);
|
|
};
|
|
static void test_runtime_metadata_and_results() {
|
|
Runtime_Device device;
|
|
Property_Object_Base& erased = device;
|
|
static_assert(!Can_Read_Command<Runtime_Device>);
|
|
static_assert(!Can_Write_Serial<Runtime_Device>);
|
|
REQUIRE(erased.runtime_object_type() == typeid(Runtime_Device));
|
|
REQUIRE(erased.runtime_property_count() == 3);
|
|
Runtime_Read_Capture capture;
|
|
REQUIRE(erased.runtime_read("value", &capture, &capture_int) == Runtime_Access_Result::ok);
|
|
REQUIRE(capture.calls == 1);
|
|
REQUIRE(capture.index == 0);
|
|
REQUIRE(capture.key == "value");
|
|
REQUIRE(capture.type != nullptr && *capture.type == typeid(int));
|
|
REQUIRE(capture.value == 10);
|
|
capture = {};
|
|
REQUIRE(erased.runtime_read("serial", &capture, &capture_int) == Runtime_Access_Result::ok);
|
|
REQUIRE(capture.calls == 1);
|
|
REQUIRE(capture.index == 1);
|
|
REQUIRE(capture.value == 7);
|
|
capture = {};
|
|
REQUIRE(erased.runtime_read("command", &capture, &capture_int) == Runtime_Access_Result::not_readable);
|
|
REQUIRE(capture.calls == 0);
|
|
REQUIRE(erased.runtime_read("missing", &capture, &capture_int) == Runtime_Access_Result::unknown_property);
|
|
REQUIRE(capture.calls == 0);
|
|
int value = 20;
|
|
REQUIRE(erased.runtime_write("value", typeid(int), &value) == Runtime_Access_Result::ok);
|
|
REQUIRE(device.value == 20);
|
|
int command = 5;
|
|
REQUIRE(erased.runtime_write("command", typeid(int), &command) == Runtime_Access_Result::ok);
|
|
REQUIRE(device.command == 5);
|
|
REQUIRE(erased.runtime_write("serial", typeid(int), &value) == Runtime_Access_Result::not_writable);
|
|
double wrong_type = 1.0;
|
|
REQUIRE(erased.runtime_write("value", typeid(double), &wrong_type) == Runtime_Access_Result::type_mismatch);
|
|
REQUIRE(erased.runtime_write("missing", typeid(int), &value) == Runtime_Access_Result::unknown_property);
|
|
}
|
|
static void test_runtime_copy_write_boundary() {
|
|
using Schema = type_descriptor_schema_t<Move_Only_Runtime_Device>;
|
|
using Property = typename Schema::template property_type<0>;
|
|
static_assert(Property::writable);
|
|
static_assert(!Property::runtime_copy_writable);
|
|
Move_Only_Runtime_Device device;
|
|
device.write<&Move_Only_Runtime_Device::value>(std::make_unique<int>(9));
|
|
REQUIRE(*device.value == 9);
|
|
Property_Object_Base& erased = device;
|
|
std::unique_ptr<int> replacement = std::make_unique<int>(11);
|
|
REQUIRE(erased.runtime_write("value", typeid(std::unique_ptr<int>), &replacement) == Runtime_Access_Result::unsupported_runtime_write);
|
|
REQUIRE(*device.value == 9);
|
|
REQUIRE(*replacement == 11);
|
|
}
|
|
static void test_runtime_access_uses_managed_synchronization() {
|
|
Runtime_Device device;
|
|
Property_Object_Base& erased = device;
|
|
std::binary_semaphore read_done{0};
|
|
std::jthread reader;
|
|
{
|
|
auto guard = device.lock_unique<&Runtime_Device::value>();
|
|
reader = std::jthread([&] {
|
|
Runtime_Read_Capture capture;
|
|
REQUIRE(erased.runtime_read("value", &capture, &capture_int) == Runtime_Access_Result::ok);
|
|
REQUIRE(capture.value == 10);
|
|
read_done.release();
|
|
});
|
|
REQUIRE(!read_done.try_acquire_for(std::chrono::milliseconds(20)));
|
|
}
|
|
REQUIRE(read_done.try_acquire_for(std::chrono::seconds(2)));
|
|
reader.join();
|
|
std::binary_semaphore write_done{0};
|
|
std::jthread writer;
|
|
{
|
|
auto guard = device.lock_shared<&Runtime_Device::value>();
|
|
writer = std::jthread([&] {
|
|
int value = 30;
|
|
REQUIRE(erased.runtime_write("value", typeid(int), &value) == Runtime_Access_Result::ok);
|
|
write_done.release();
|
|
});
|
|
REQUIRE(!write_done.try_acquire_for(std::chrono::milliseconds(20)));
|
|
}
|
|
REQUIRE(write_done.try_acquire_for(std::chrono::seconds(2)));
|
|
writer.join();
|
|
REQUIRE(device.value == 30);
|
|
}
|
|
static void test_runtime_read_only_fast_path() {
|
|
Runtime_Device device;
|
|
Property_Object_Base& erased = device;
|
|
REQUIRE(device.lock_slot<&Runtime_Device::serial>() == Resolved_Synchronization_View::unsynchronized_slot);
|
|
std::binary_semaphore done{0};
|
|
std::jthread reader;
|
|
{
|
|
auto guard = device.lock_unique<&Runtime_Device::value>();
|
|
reader = std::jthread([&] {
|
|
Runtime_Read_Capture capture;
|
|
REQUIRE(erased.runtime_read("serial", &capture, &capture_int) == Runtime_Access_Result::ok);
|
|
REQUIRE(capture.value == 7);
|
|
done.release();
|
|
});
|
|
REQUIRE(done.try_acquire_for(std::chrono::milliseconds(200)));
|
|
}
|
|
reader.join();
|
|
}
|
|
int main() {
|
|
test_runtime_metadata_and_results();
|
|
test_runtime_copy_write_boundary();
|
|
test_runtime_access_uses_managed_synchronization();
|
|
test_runtime_read_only_fast_path();
|
|
}
|