#include #include #include #include namespace { struct probe_device { constexpr auto static id = kapi::devices::interface_id{"probe"}; virtual ~probe_device() = default; [[nodiscard]] auto virtual get_value() const noexcept -> int = 0; }; struct const_device { constexpr auto static id = kapi::devices::interface_id{"flip"}; virtual ~const_device() = default; [[nodiscard]] auto get_constant() const noexcept -> int { return 0; } }; struct unimplemented_device { constexpr auto static id = kapi::devices::interface_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_interface(kapi::devices::interface_id interface) -> void * override { if (interface == probe_device::id) { return static_cast(this); } if (interface == const_device::id) { return m_const_device; } return kapi::devices::device::query_interface(interface); } private: int m_value; const_device * m_const_device; }; struct counting_observer final : kapi::devices::interface_registry_observer { auto on_interface_published(kapi::devices::interface_id, kapi::devices::interface_registry::entry const &) -> void override { ++published; } auto on_interface_withdrawn(kapi::devices::interface_id, kapi::devices::device &) -> void override { ++withdrawn; } std::size_t published{}; std::size_t withdrawn{}; }; struct evil_observer final : kapi::devices::interface_registry_observer { explicit evil_observer(kstd::shared_ptr & victim) : m_victim{victim} {} auto on_interface_published(kapi::devices::interface_id, kapi::devices::interface_registry::entry const &) -> void override { m_victim.reset(); } auto on_interface_withdrawn(kapi::devices::interface_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][interface_registry]") { GIVEN("An empty registry, a device, and an interface the device implements by inheritance") { auto registry = kapi::devices::interface_registry{}; auto free_standing_interface = const_device{}; auto device = kstd::make_shared(128, free_standing_interface); THEN("Publishing an interface without a name fails") { REQUIRE_FALSE(registry.publish(device, "")); } WHEN("publishing the interface for the device") { CHECK(device->is_a()); CHECK(device->is_a()); auto published = registry.publish(device, "probe0", device->as()); THEN("publishing is successful") { REQUIRE(published); } THEN("publishing the same device and interface with a different name fails") { REQUIRE_FALSE(registry.publish(device, "probe1", device->as())); } THEN("publishing a second interface for the same device succeeds") { REQUIRE(registry.publish(device, "probe0")); } THEN("publishing a free standing second interface for the same device succeeds") { REQUIRE(registry.publish(device, "probe0", &free_standing_interface)); } AND_WHEN("getting all devices implementing that interface") { 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].implementation() == device->as()); } THEN("the interface provided function can be invoked") { auto implementation = probeable_devices[0].as(); 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 interface 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->as())); 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::interface_registry{}; THEN("all() returns an empty vector") { REQUIRE(registry.all(probe_device::id).empty()); } } GIVEN("a null device") { auto registry = kapi::devices::interface_registry{}; auto device = kstd::shared_ptr{}; auto interface = const_device{}; THEN("publishing the interface for the device fails") { REQUIRE_FALSE(registry.publish(device, "probe0", &interface)); } } } SCENARIO("Interface registry notifies subscribers", "[kapi][devices][interface_registry]") { GIVEN("a device and a subscribed observer") { auto registry = kapi::devices::interface_registry{}; auto free_standing_interface = const_device{}; auto device = kstd::make_shared(128, free_standing_interface); auto observer = kstd::make_shared(); registry.subscribe(observer); WHEN("an interface is published for the device") { CHECK(registry.publish(device, "probe0", device->as())); THEN("the observer is notified exactly once") { REQUIRE(observer->published == 1); REQUIRE(observer->withdrawn == 0); } } WHEN("an interface is published and then withdrawn") { CHECK(registry.publish(device, "probe0", device->as())); 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->as())); } } WHEN("the observer unsubscribes before anything is published") { registry.unsubscribe(*observer); THEN("publishing afterward does not crash") { REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as())); } THEN("the subscriber is not notified") { REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as())); 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::interface_registry{}; auto free_standing_interface = const_device{}; auto device = kstd::make_shared(128, free_standing_interface); auto second = kstd::make_shared(); auto first = kstd::make_shared(second); registry.subscribe(first); registry.subscribe(second); THEN("publishing an interface does not crash") { REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as())); REQUIRE(second == nullptr); } } }