#include #include #include #include namespace { struct test_bus final : kapi::devices::bus { using kapi::devices::bus::bus; }; struct test_device final : kapi::devices::device { using kapi::devices::device::device; }; struct flagging_device final : kapi::devices::device { flagging_device(kstd::string const & name, bool & destroyed_flag) : device{name} , m_destroyed_flag{&destroyed_flag} {} ~flagging_device() override { *m_destroyed_flag = true; } private: bool * m_destroyed_flag; }; } // namespace SCENARIO("A bus shares ownership of its children, holding a weak link to its parent", "[devices][bus]") { GIVEN("A bus with one attached child device") { auto parent_bus = kstd::make_shared("bus_ownership_parent"); auto child = kstd::make_shared("bus_ownership_child"); parent_bus->add_child(child); THEN("the child is reachable through children()") { REQUIRE(parent_bus->children().size() == 1); REQUIRE(parent_bus->children()[0]->name() == "bus_ownership_child"); } THEN("the child's parent resolves back to the bus") { auto locked_parent = child->parent(); REQUIRE(locked_parent); REQUIRE(locked_parent->name() == "bus_ownership_parent"); } WHEN("the bus is destroyed while the caller still holds the child directly") { parent_bus.reset(); THEN("the child outlives the bus") { REQUIRE(child != nullptr); } THEN("the child's parent link no longer resolves") { REQUIRE(child->parent() == nullptr); } } } GIVEN("a bus that is the only thing keeping its child alive") { auto destroyed = false; auto parent_bus = kstd::make_shared("bus_lifetime_parent"); parent_bus->add_child(kstd::make_shared("bus_lifetime_child", destroyed)); WHEN("the bus is destroyed") { CHECK_FALSE(destroyed); parent_bus.reset(); THEN("the child is destroyed along with it") { REQUIRE(destroyed); } } } } SCENARIO("Removing a child from a bus tears down the while subtree", "[devices][bus]") { GIVEN("A bus with a child bus") { auto root = kstd::make_shared("bus_removal_root"); auto middle = kstd::make_shared("bus_removal_middle"); auto leaf_destroyed = false; auto leaf = kstd::make_shared("bus_removal_leaf", leaf_destroyed); root->add_child(middle); middle->add_child(leaf); REQUIRE(root->children().size() == 1); REQUIRE(middle->children().size() == 1); WHEN("the middle bus is removed from the root") { REQUIRE(root->remove_child(*middle)); THEN("the middle bus's own subtree was torn down") { REQUIRE(middle->children().empty()); } THEN("the leaf is marked as removed but not destroyed") { REQUIRE(leaf->state() == kapi::devices::state::removed); REQUIRE(leaf->parent() == nullptr); REQUIRE_FALSE(leaf_destroyed); } AND_WHEN("dropping the outside leaf reference") { leaf.reset(); THEN("the leaf is destroyed") { REQUIRE(leaf_destroyed); } } THEN("the root no longer considers the middle its child") { REQUIRE(root->children().empty()); REQUIRE(middle->state() == kapi::devices::state::removed); } } } GIVEN("A bus and a device that was never actually attached to it") { auto bus = kstd::make_shared("bus_removal_unrelated_bus"); auto unrelated = kstd::make_shared("bus_removal_unrelated_device"); THEN("removing the child fails") { REQUIRE_FALSE(bus->remove_child(*unrelated)); } } }