Files
Structive/core/tests/runtime_api_test.cpp
T
2026-08-08 21:21:23 +08:00

165 lines
6.6 KiB
C++

#include <structive/property/property.hpp>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <semaphore>
#include <memory>
#include <string>
#include <thread>
#include <typeinfo>
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 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();
}