aboutsummaryrefslogtreecommitdiff
path: root/kernel/kapi/devices/facet_registry.tests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/kapi/devices/facet_registry.tests.cpp')
-rw-r--r--kernel/kapi/devices/facet_registry.tests.cpp327
1 files changed, 327 insertions, 0 deletions
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 <kapi/devices.hpp>
+
+#include <kstd/memory.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <cstddef>
+
+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<probe_device *>(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<counting_observer> & 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<counting_observer> & 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<test_device>(128, free_standing_facet);
+
+ THEN("Publishing an facet without a name fails")
+ {
+ REQUIRE_FALSE(registry.publish<probe_device>(device, ""));
+ }
+
+ WHEN("publishing the facet for the device")
+ {
+ CHECK(device->has_facet<probe_device>());
+ CHECK(device->has_facet<const_device>());
+ auto published = registry.publish(device, "probe0", device->facet<probe_device>());
+
+ 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<probe_device>()));
+ }
+
+ THEN("publishing a second facet for the same device succeeds")
+ {
+ REQUIRE(registry.publish<const_device>(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<probe_device>());
+ }
+
+ THEN("the facet provided function can be invoked")
+ {
+ auto implementation = probeable_devices[0].facet<probe_device>();
+ REQUIRE(implementation->get_value() == device->get_value());
+ }
+ }
+
+ THEN("resolve finds the published capability by name")
+ {
+ REQUIRE(registry.resolve<probe_device>("probe0"));
+ }
+
+ THEN("resolve finds a capability the device implements but never published")
+ {
+ REQUIRE(registry.resolve<const_device>("probe0"));
+ }
+
+ THEN("resolve for a capability the device implements nowhere at all does not find anything")
+ {
+ REQUIRE_FALSE(registry.resolve<unimplemented_device>("probe0"));
+ }
+
+ THEN("resolve for an unpublished device does not find a device")
+ {
+ REQUIRE_FALSE(registry.resolve<probe_device>("probe1"));
+ }
+
+ THEN("withdrawing an facet for a device removes it from the registry")
+ {
+ registry.withdraw(*device, probe_device::id);
+ REQUIRE_FALSE(registry.resolve<probe_device>("probe0"));
+ }
+ }
+
+ WHEN("the device is destroyed without withdrawing it")
+ {
+ CHECK(registry.publish(device, "probe0", device->facet<probe_device>()));
+ 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<probe_device>("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<kapi::devices::device>{};
+ 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<test_device>(128, free_standing_facet);
+ auto observer = kstd::make_shared<counting_observer>();
+
+ registry.subscribe(observer);
+
+ WHEN("an facet is published for the device")
+ {
+ CHECK(registry.publish(device, "probe0", device->facet<probe_device>()));
+
+ 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<probe_device>()));
+ 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<probe_device>()));
+ }
+ }
+
+ 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<probe_device>()));
+ }
+
+ THEN("the subscriber is not notified")
+ {
+ REQUIRE_NOTHROW(registry.publish(device, "probe0", device->facet<probe_device>()));
+ 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<test_device>(128, free_standing_facet);
+
+ auto second = kstd::make_shared<counting_observer>();
+ auto first = kstd::make_shared<evil_observer>(second);
+
+ registry.subscribe(first);
+ registry.subscribe(second);
+
+ THEN("publishing an facet does not crash")
+ {
+ REQUIRE_NOTHROW(registry.publish(device, "probe0", device->facet<probe_device>()));
+ REQUIRE(second == nullptr);
+ }
+ }
+} \ No newline at end of file