#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); } } }