From 59f54d012c71e782966e9d083f1ee9459fd3fd70 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Sun, 26 Jul 2026 02:27:59 +0200 Subject: chore: replace "interface" with "facet" --- kernel/kapi/devices/facet_registry.tests.cpp | 327 +++++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 kernel/kapi/devices/facet_registry.tests.cpp (limited to 'kernel/kapi/devices/facet_registry.tests.cpp') diff --git a/kernel/kapi/devices/facet_registry.tests.cpp b/kernel/kapi/devices/facet_registry.tests.cpp new file mode 100644 index 00000000..4ad3f763 --- /dev/null +++ b/kernel/kapi/devices/facet_registry.tests.cpp @@ -0,0 +1,327 @@ +#include + +#include + +#include + +#include + +namespace +{ + struct probe_device + { + constexpr auto static id = kapi::capabilities::facet_id{"probe"}; + + virtual ~probe_device() = default; + + [[nodiscard]] auto virtual get_value() const noexcept -> int = 0; + }; + + struct const_device + { + constexpr auto static id = kapi::capabilities::facet_id{"flip"}; + + virtual ~const_device() = default; + + [[nodiscard]] auto get_constant() const noexcept -> int + { + return 0; + } + }; + + struct unimplemented_device + { + constexpr auto static id = kapi::capabilities::facet_id{"unimplemented"}; + + virtual ~unimplemented_device() = default; + }; + + struct test_device final : kapi::devices::device, probe_device + { + explicit test_device(int value, const_device & const_device) + : device{"probeable"} + , m_value{value} + , m_const_device{&const_device} + {} + + [[nodiscard]] constexpr auto get_value() const noexcept -> int override + { + return m_value; + } + + auto query_facet(kapi::capabilities::facet_id facet) -> void * override + { + if (facet == probe_device::id) + { + return static_cast(this); + } + if (facet == const_device::id) + { + return m_const_device; + } + + return kapi::devices::device::query_facet(facet); + } + + private: + int m_value; + const_device * m_const_device; + }; + + struct counting_observer final : kapi::devices::facet_registry_observer + { + auto on_facet_published(kapi::capabilities::facet_id, kapi::devices::facet_registry::entry const &) -> void override + { + ++published; + } + + auto on_facet_withdrawn(kapi::capabilities::facet_id, kapi::devices::device &) -> void override + { + ++withdrawn; + } + + std::size_t published{}; + std::size_t withdrawn{}; + }; + + struct evil_observer final : kapi::devices::facet_registry_observer + { + explicit evil_observer(kstd::shared_ptr & victim) + : m_victim{victim} + {} + + auto on_facet_published(kapi::capabilities::facet_id, kapi::devices::facet_registry::entry const &) -> void override + { + m_victim.reset(); + } + + auto on_facet_withdrawn(kapi::capabilities::facet_id, kapi::devices::device &) -> void override + { + m_victim.reset(); + } + + private: + kstd::shared_ptr & m_victim; // NOLINT + }; + +} // namespace + +SCENARIO("Publishing and finding a device", "[kapi][devices][facet_registry]") +{ + GIVEN("An empty registry, a device, and a facet the device implements by inheritance") + { + auto registry = kapi::devices::facet_registry{}; + auto free_standing_facet = const_device{}; + auto device = kstd::make_shared(128, free_standing_facet); + + THEN("Publishing an facet without a name fails") + { + REQUIRE_FALSE(registry.publish(device, "")); + } + + WHEN("publishing the facet for the device") + { + CHECK(device->has_facet()); + CHECK(device->has_facet()); + auto published = registry.publish(device, "probe0", device->facet()); + + THEN("publishing is successful") + { + REQUIRE(published); + } + + THEN("publishing the same device and facet with a different name fails") + { + REQUIRE_FALSE(registry.publish(device, "probe1", device->facet())); + } + + THEN("publishing a second facet for the same device succeeds") + { + REQUIRE(registry.publish(device, "probe0")); + } + + THEN("publishing a free standing second facet for the same device succeeds") + { + REQUIRE(registry.publish(device, "probe0", &free_standing_facet)); + } + + AND_WHEN("getting all devices implementing that facet") + { + auto probeable_devices = registry.all(probe_device::id); + + THEN("there is exactly one such device") + { + REQUIRE(probeable_devices.size() == 1); + } + + THEN("the name of the device is 'probe0'") + { + REQUIRE(probeable_devices[0].name() == "probe0"); + } + + THEN("the returned device pointer is valid") + { + REQUIRE(probeable_devices[0].device()); + } + + THEN("the returned implementation equals the result of device::as") + { + REQUIRE(probeable_devices[0].untyped_facet() == device->facet()); + } + + THEN("the facet provided function can be invoked") + { + auto implementation = probeable_devices[0].facet(); + REQUIRE(implementation->get_value() == device->get_value()); + } + } + + THEN("resolve finds the published capability by name") + { + REQUIRE(registry.resolve("probe0")); + } + + THEN("resolve finds a capability the device implements but never published") + { + REQUIRE(registry.resolve("probe0")); + } + + THEN("resolve for a capability the device implements nowhere at all does not find anything") + { + REQUIRE_FALSE(registry.resolve("probe0")); + } + + THEN("resolve for an unpublished device does not find a device") + { + REQUIRE_FALSE(registry.resolve("probe1")); + } + + THEN("withdrawing an facet for a device removes it from the registry") + { + registry.withdraw(*device, probe_device::id); + REQUIRE_FALSE(registry.resolve("probe0")); + } + } + + WHEN("the device is destroyed without withdrawing it") + { + CHECK(registry.publish(device, "probe0", device->facet())); + device.reset(); + + THEN("querying all does no longer report it") + { + REQUIRE(registry.all(probe_device::id).empty()); + } + + THEN("resolve() no longer finds it") + { + REQUIRE_FALSE(registry.resolve("probe0")); + } + } + } + + GIVEN("no device has ever been published") + { + auto registry = kapi::devices::facet_registry{}; + + THEN("all() returns an empty vector") + { + REQUIRE(registry.all(probe_device::id).empty()); + } + } + + GIVEN("a null device") + { + auto registry = kapi::devices::facet_registry{}; + auto device = kstd::shared_ptr{}; + auto facet = const_device{}; + + THEN("publishing the facet for the device fails") + { + REQUIRE_FALSE(registry.publish(device, "probe0", &facet)); + } + } +} + +SCENARIO("facet registry notifies subscribers", "[kapi][devices][facet_registry]") +{ + GIVEN("a device and a subscribed observer") + { + auto registry = kapi::devices::facet_registry{}; + auto free_standing_facet = const_device{}; + auto device = kstd::make_shared(128, free_standing_facet); + auto observer = kstd::make_shared(); + + registry.subscribe(observer); + + WHEN("an facet is published for the device") + { + CHECK(registry.publish(device, "probe0", device->facet())); + + THEN("the observer is notified exactly once") + { + REQUIRE(observer->published == 1); + REQUIRE(observer->withdrawn == 0); + } + } + + WHEN("an facet is published and then withdrawn") + { + CHECK(registry.publish(device, "probe0", device->facet())); + registry.withdraw(*device, probe_device::id); + + THEN("the observer is notified of both exactly one") + { + REQUIRE(observer->published == 1); + REQUIRE(observer->withdrawn == 1); + } + } + + WHEN("the observer is destroyed before anything is published") + { + observer.reset(); + + THEN("publishing afterward does not crash") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->facet())); + } + } + + WHEN("the observer unsubscribes before anything is published") + { + registry.unsubscribe(*observer); + + THEN("publishing afterward does not crash") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->facet())); + } + + THEN("the subscriber is not notified") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->facet())); + REQUIRE_NOTHROW(registry.withdraw(*device, probe_device::id)); + REQUIRE(observer->published == 0); + REQUIRE(observer->withdrawn == 0); + } + } + } + + GIVEN("two subscriber, the first destroying the second") + { + auto registry = kapi::devices::facet_registry{}; + auto free_standing_facet = const_device{}; + auto device = kstd::make_shared(128, free_standing_facet); + + auto second = kstd::make_shared(); + auto first = kstd::make_shared(second); + + registry.subscribe(first); + registry.subscribe(second); + + THEN("publishing an facet does not crash") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->facet())); + REQUIRE(second == nullptr); + } + } +} \ No newline at end of file -- cgit v1.2.3