| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/devices/bin/driver_manager/node.h" |
| |
| #include <fidl/fuchsia.driver.host/cpp/test_base.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/default.h> |
| #include <lib/async_patterns/testing/cpp/dispatcher_bound.h> |
| #include <lib/sync/cpp/completion.h> |
| |
| #include <bind/fuchsia/platform/cpp/bind.h> |
| |
| #include "src/devices/bin/driver_manager/driver_host.h" |
| #include "src/devices/bin/driver_manager/tests/driver_manager_test_base.h" |
| |
| class TestRealm final : public fidl::WireServer<fuchsia_component::Realm> { |
| public: |
| TestRealm(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {} |
| |
| fidl::ClientEnd<fuchsia_component::Realm> Connect() { |
| auto [client_end, server_end] = fidl::Endpoints<fuchsia_component::Realm>::Create(); |
| fidl::BindServer(dispatcher_, std::move(server_end), this); |
| return std::move(client_end); |
| } |
| |
| void OpenExposedDir(OpenExposedDirRequestView request, |
| OpenExposedDirCompleter::Sync& completer) override {} |
| |
| void CreateChild(CreateChildRequestView request, CreateChildCompleter::Sync& completer) override { |
| } |
| |
| void DestroyChild(DestroyChildRequestView request, |
| DestroyChildCompleter::Sync& completer) override { |
| completer.ReplySuccess(); |
| } |
| |
| void ListChildren(ListChildrenRequestView request, |
| ListChildrenCompleter::Sync& completer) override {} |
| |
| private: |
| async_dispatcher_t* dispatcher_; |
| }; |
| |
| class FakeDriverHost : public driver_manager::DriverHost { |
| public: |
| using StartCallback = fit::callback<void(zx::result<>)>; |
| void Start(fidl::ClientEnd<fuchsia_driver_framework::Node> client_end, std::string node_name, |
| fuchsia_driver_framework::wire::NodePropertyDictionary node_properties, |
| fidl::VectorView<fuchsia_driver_framework::wire::NodeSymbol> symbols, |
| fuchsia_component_runner::wire::ComponentStartInfo start_info, |
| fidl::ServerEnd<fuchsia_driver_host::Driver> driver, StartCallback cb) override { |
| drivers_[node_name] = std::move(driver); |
| clients_[node_name] = std::move(client_end); |
| cb(zx::ok()); |
| } |
| |
| zx::result<uint64_t> GetProcessKoid() const override { return zx::error(ZX_ERR_NOT_SUPPORTED); } |
| |
| void CloseDriver(std::string node_name) { |
| drivers_[node_name].Close(ZX_OK); |
| clients_[node_name].reset(); |
| } |
| |
| std::tuple<fidl::ServerEnd<fuchsia_driver_host::Driver>, |
| fidl::ClientEnd<fuchsia_driver_framework::Node>> |
| TakeDriver(const std::string& node_name) { |
| auto driver = std::move(drivers_[node_name]); |
| auto client = std::move(clients_[node_name]); |
| drivers_.erase(node_name); |
| clients_.erase(node_name); |
| return std::make_tuple(std::move(driver), std::move(client)); |
| } |
| |
| private: |
| std::unordered_map<std::string, fidl::ServerEnd<fuchsia_driver_host::Driver>> drivers_; |
| std::unordered_map<std::string, fidl::ClientEnd<fuchsia_driver_framework::Node>> clients_; |
| }; |
| |
| class FakeNodeManager : public TestNodeManagerBase { |
| public: |
| FakeNodeManager(fidl::WireClient<fuchsia_component::Realm> realm) : realm_(std::move(realm)) {} |
| |
| zx::result<driver_manager::DriverHost*> CreateDriverHost(bool use_next_vdso) override { |
| return zx::ok(&driver_host_); |
| } |
| |
| void DestroyDriverComponent( |
| driver_manager::Node& node, |
| fit::callback<void(fidl::WireUnownedResult<fuchsia_component::Realm::DestroyChild>& result)> |
| callback) override { |
| auto name = node.MakeComponentMoniker(); |
| fuchsia_component_decl::wire::ChildRef child_ref{ |
| .name = fidl::StringView::FromExternal(name), |
| .collection = "", |
| }; |
| realm_->DestroyChild(child_ref).Then(std::move(callback)); |
| clients_.erase(node.name()); |
| } |
| |
| void CloseDriverForNode(std::string node_name) { driver_host_.CloseDriver(node_name); } |
| |
| FakeDriverHost& driver_host() { return driver_host_; } |
| |
| void AddClient(const std::string& node_name, |
| fidl::ClientEnd<fuchsia_component_runner::ComponentController> client) { |
| clients_[node_name] = std::move(client); |
| } |
| |
| private: |
| fidl::WireClient<fuchsia_component::Realm> realm_; |
| std::unordered_map<std::string, fidl::ClientEnd<fuchsia_component_runner::ComponentController>> |
| clients_; |
| FakeDriverHost driver_host_; |
| }; |
| |
| class FakeDriver : public fidl::testing::TestBase<fuchsia_driver_host::Driver> { |
| public: |
| using StopCallback = fit::function<void()>; |
| |
| FakeDriver( |
| async_dispatcher_t* dispatcher, fidl::ServerEnd<fuchsia_driver_host::Driver> server_end, |
| fidl::ClientEnd<fuchsia_driver_framework::Node> node, StopCallback stop_callback = []() {}) |
| : binding_(async_get_default_dispatcher(), std::move(server_end), this, |
| fidl::kIgnoreBindingClosure), |
| node_(std::move(node)), |
| stop_callback_(std::move(stop_callback)) {} |
| |
| void Stop(StopCompleter::Sync& completer) override { |
| stop_callback_(); |
| node_.reset(); |
| binding_.Close(ZX_OK); |
| } |
| |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| printf("Not implemented: Driver::%s\n", name.c_str()); |
| } |
| |
| private: |
| fidl::ServerBinding<fuchsia_driver_host::Driver> binding_; |
| fidl::ClientEnd<fuchsia_driver_framework::Node> node_; |
| StopCallback stop_callback_; |
| }; |
| |
| class Dfv2NodeTest : public DriverManagerTestBase { |
| public: |
| struct StartDriverOptions { |
| bool host_restart_on_crash; |
| }; |
| |
| void SetUp() override { |
| DriverManagerTestBase::SetUp(); |
| realm_ = std::make_unique<TestRealm>(dispatcher()); |
| |
| auto client = realm_->Connect(); |
| node_manager = std::make_unique<FakeNodeManager>( |
| fidl::WireClient<fuchsia_component::Realm>(std::move(client), dispatcher())); |
| } |
| |
| void StartTestDriver(std::shared_ptr<driver_manager::Node> node, |
| StartDriverOptions options = {.host_restart_on_crash = false}) { |
| std::vector<fuchsia_data::DictionaryEntry> program_entries = { |
| {{ |
| .key = "binary", |
| .value = std::make_unique<fuchsia_data::DictionaryValue>( |
| fuchsia_data::DictionaryValue::WithStr("driver/library.so")), |
| }}, |
| {{ |
| .key = "colocate", |
| .value = std::make_unique<fuchsia_data::DictionaryValue>( |
| fuchsia_data::DictionaryValue::WithStr("false")), |
| }}, |
| }; |
| |
| if (options.host_restart_on_crash) { |
| program_entries.emplace_back(fuchsia_data::DictionaryEntry({ |
| .key = "host_restart_on_crash", |
| .value = std::make_unique<fuchsia_data::DictionaryValue>( |
| fuchsia_data::DictionaryValue::WithStr("true")), |
| })); |
| } |
| |
| auto [_, server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| auto start_info = fuchsia_component_runner::ComponentStartInfo{{ |
| .resolved_url = "fuchsia-boot:///#meta/test-driver.cm", |
| .program = fuchsia_data::Dictionary{{.entries = std::move(program_entries)}}, |
| .outgoing_dir = std::move(server_end), |
| }}; |
| |
| auto controller_endpoints = |
| fidl::Endpoints<fuchsia_component_runner::ComponentController>::Create(); |
| |
| node_manager->AddClient(node->name(), std::move(controller_endpoints.client)); |
| |
| fidl::Arena arena; |
| node->StartDriver(fidl::ToWire(arena, std::move(start_info)), |
| std::move(controller_endpoints.server), |
| [node](zx::result<> result) { node->CompleteBind(result); }); |
| } |
| |
| protected: |
| fidl::WireClient<fuchsia_device::Controller> ConnectToDeviceController( |
| std::shared_ptr<driver_manager::Node> node) { |
| zx::result device_controller_endpoints = fidl::CreateEndpoints<fuchsia_device::Controller>(); |
| EXPECT_EQ(device_controller_endpoints.status_value(), ZX_OK); |
| device_controller_bindings_.AddBinding(dispatcher(), |
| std::move(device_controller_endpoints->server), |
| node.get(), fidl::kIgnoreBindingClosure); |
| return {std::move(device_controller_endpoints->client), dispatcher()}; |
| } |
| |
| driver_manager::NodeManager* GetNodeManager() override { return node_manager.get(); } |
| |
| std::unique_ptr<FakeNodeManager> node_manager; |
| |
| private: |
| std::unique_ptr<TestRealm> realm_; |
| fidl::ServerBindingGroup<fuchsia_device::Controller> device_controller_bindings_; |
| }; |
| |
| TEST_F(Dfv2NodeTest, RemoveDuringFailedBind) { |
| auto node = CreateNode("test"); |
| StartTestDriver(node); |
| ASSERT_TRUE(node->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, node->GetNodeState()); |
| |
| node->Remove(driver_manager::RemovalSet::kAll, nullptr); |
| RunLoopUntilIdle(); |
| |
| ASSERT_EQ(driver_manager::NodeState::kWaitingOnDriver, node->GetNodeState()); |
| |
| node->CompleteBind(zx::error(ZX_ERR_NOT_FOUND)); |
| RunLoopUntilIdle(); |
| ASSERT_FALSE(node->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kStopped, node->GetNodeState()); |
| } |
| |
| TEST_F(Dfv2NodeTest, TestEvaluateRematchFlags) { |
| auto node = CreateNode("plain"); |
| ASSERT_FALSE(node->EvaluateRematchFlags( |
| fuchsia_driver_development::RestartRematchFlags::kRequested, "some-url")); |
| ASSERT_TRUE( |
| node->EvaluateRematchFlags(fuchsia_driver_development::RestartRematchFlags::kRequested | |
| fuchsia_driver_development::RestartRematchFlags::kNonRequested, |
| "some-url")); |
| |
| auto parent_1 = CreateNode("p1"); |
| auto parent_2 = CreateNode("p2"); |
| |
| auto composite = CreateCompositeNode("composite", {parent_1, parent_2}, {{}, {}}, |
| /* primary_index */ 0); |
| |
| ASSERT_FALSE(composite->EvaluateRematchFlags( |
| fuchsia_driver_development::RestartRematchFlags::kRequested | |
| fuchsia_driver_development::RestartRematchFlags::kNonRequested, |
| "some-url")); |
| ASSERT_FALSE(composite->EvaluateRematchFlags( |
| fuchsia_driver_development::RestartRematchFlags::kRequested | |
| fuchsia_driver_development::RestartRematchFlags::kNonRequested | |
| fuchsia_driver_development::RestartRematchFlags::kLegacyComposite, |
| "some-url")); |
| } |
| |
| TEST_F(Dfv2NodeTest, RemoveCompositeNodeForRebind) { |
| auto parent_node_1 = CreateNode("parent_1"); |
| StartTestDriver(parent_node_1); |
| ASSERT_TRUE(parent_node_1->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, parent_node_1->GetNodeState()); |
| |
| auto parent_node_2 = CreateNode("parent_2"); |
| StartTestDriver(parent_node_2); |
| ASSERT_TRUE(parent_node_2->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, parent_node_2->GetNodeState()); |
| |
| auto composite = CreateCompositeNode("composite", {parent_node_1, parent_node_2}, {{}, {}}); |
| StartTestDriver(composite); |
| ASSERT_TRUE(composite->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, composite->GetNodeState()); |
| |
| ASSERT_EQ(1u, parent_node_1->children().size()); |
| ASSERT_EQ(1u, parent_node_2->children().size()); |
| |
| auto remove_callback_succeeded = false; |
| composite->RemoveCompositeNodeForRebind([&remove_callback_succeeded](zx::result<> result) { |
| if (result.is_ok()) { |
| remove_callback_succeeded = true; |
| } |
| }); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(driver_manager::NodeState::kWaitingOnDriver, composite->GetNodeState()); |
| ASSERT_EQ(driver_manager::ShutdownIntent::kRebindComposite, composite->shutdown_intent()); |
| |
| node_manager->CloseDriverForNode("composite"); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(remove_callback_succeeded); |
| |
| ASSERT_EQ(driver_manager::NodeState::kStopped, composite->GetNodeState()); |
| } |
| |
| // Verify that we receives a callback for composite rebind if the node is deallocated |
| // before shutdown is complete. |
| TEST_F(Dfv2NodeTest, RemoveCompositeNodeForRebind_Dealloc) { |
| auto parent_node_1 = CreateNode("parent_1"); |
| StartTestDriver(parent_node_1); |
| ASSERT_TRUE(parent_node_1->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, parent_node_1->GetNodeState()); |
| |
| auto parent_node_2 = CreateNode("parent_2"); |
| StartTestDriver(parent_node_2); |
| ASSERT_TRUE(parent_node_2->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, parent_node_2->GetNodeState()); |
| |
| auto composite = CreateCompositeNode("composite", {parent_node_1, parent_node_2}, {{}, {}}); |
| StartTestDriver(composite); |
| ASSERT_TRUE(composite->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, composite->GetNodeState()); |
| |
| ASSERT_EQ(1u, parent_node_1->children().size()); |
| ASSERT_EQ(1u, parent_node_2->children().size()); |
| |
| bool is_cancelled = false; |
| composite->RemoveCompositeNodeForRebind([&is_cancelled](zx::result<> result) { |
| if (result.is_error()) { |
| is_cancelled = result.error_value() == ZX_ERR_CANCELED; |
| } |
| }); |
| RunLoopUntilIdle(); |
| ASSERT_EQ(driver_manager::NodeState::kWaitingOnDriver, composite->GetNodeState()); |
| ASSERT_EQ(driver_manager::ShutdownIntent::kRebindComposite, composite->shutdown_intent()); |
| |
| parent_node_1.reset(); |
| parent_node_2.reset(); |
| composite.reset(); |
| RunLoopUntilIdle(); |
| ASSERT_TRUE(is_cancelled); |
| } |
| |
| TEST_F(Dfv2NodeTest, RestartOnCrashComposite) { |
| auto parent_node_1 = CreateNode("parent_1"); |
| StartTestDriver(parent_node_1); |
| ASSERT_TRUE(parent_node_1->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, parent_node_1->GetNodeState()); |
| |
| auto parent_node_2 = CreateNode("parent_2"); |
| StartTestDriver(parent_node_2); |
| ASSERT_TRUE(parent_node_2->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, parent_node_2->GetNodeState()); |
| |
| auto composite = CreateCompositeNode("composite", {parent_node_1, parent_node_2}, {{}, {}}); |
| StartTestDriver(composite, {.host_restart_on_crash = true}); |
| |
| ASSERT_TRUE(composite->HasDriverComponent()); |
| ASSERT_EQ(driver_manager::NodeState::kRunning, composite->GetNodeState()); |
| |
| ASSERT_EQ(1u, parent_node_1->children().size()); |
| ASSERT_EQ(1u, parent_node_2->children().size()); |
| |
| // Simulate a crash by closing the driver side of channels. |
| node_manager->CloseDriverForNode("composite"); |
| RunLoopUntilIdle(); |
| |
| // The node should come back to running state. |
| ASSERT_EQ(driver_manager::NodeState::kRunning, composite->GetNodeState()); |
| } |
| |
| TEST_F(Dfv2NodeTest, TestCompositeNodeProperties) { |
| const char* kParent1Name = "parent-1"; |
| const std::vector<fuchsia_driver_framework::NodeProperty> kParent1NodeProperties{ |
| fuchsia_driver_framework::NodeProperty( |
| fuchsia_driver_framework::NodePropertyKey::WithIntValue(1), |
| fuchsia_driver_framework::NodePropertyValue::WithIntValue(2))}; |
| const char* kParent2Name = "parent-2"; |
| const std::vector<fuchsia_driver_framework::NodeProperty> kParent2NodeProperties{ |
| fuchsia_driver_framework::NodeProperty( |
| fuchsia_driver_framework::NodePropertyKey::WithStringValue("test-key"), |
| fuchsia_driver_framework::NodePropertyValue::WithStringValue("test-value"))}; |
| auto parent_1 = CreateNode(kParent1Name); |
| parent_1->SetNonCompositeProperties(kParent1NodeProperties); |
| auto parent_2 = CreateNode(kParent2Name); |
| parent_2->SetNonCompositeProperties(kParent2NodeProperties); |
| |
| std::vector<fuchsia_driver_framework::NodePropertyEntry> parent_properties; |
| parent_properties.emplace_back(kParent1Name, kParent1NodeProperties); |
| parent_properties.emplace_back(kParent2Name, kParent2NodeProperties); |
| auto composite = CreateCompositeNode("composite", {parent_1, parent_2}, parent_properties, |
| /* primary_index */ 0); |
| |
| // Verify primary parent properties. Primary parent should be parent 1. |
| const auto& primary_parent_node_properties = composite->GetNodeProperties(); |
| ASSERT_TRUE(primary_parent_node_properties.has_value()); |
| ASSERT_EQ(1ul, primary_parent_node_properties->size()); |
| |
| const auto& primary_parent_node_property_1 = primary_parent_node_properties.value()[0]; |
| ASSERT_TRUE(primary_parent_node_property_1.key.is_int_value()); |
| ASSERT_EQ(kParent1NodeProperties[0].key().int_value().value(), |
| primary_parent_node_property_1.key.int_value()); |
| ASSERT_TRUE(primary_parent_node_property_1.value.is_int_value()); |
| ASSERT_EQ(kParent1NodeProperties[0].value().int_value().value(), |
| primary_parent_node_property_1.value.int_value()); |
| |
| // Verify parent 1 properties. |
| const auto& parent_1_node_properties = composite->GetNodeProperties(kParent1Name); |
| ASSERT_TRUE(parent_1_node_properties.has_value()); |
| ASSERT_EQ(1ul, parent_1_node_properties->size()); |
| |
| const auto& parent_1_node_property_1 = parent_1_node_properties.value()[0]; |
| ASSERT_TRUE(parent_1_node_property_1.key.is_int_value()); |
| ASSERT_EQ(kParent1NodeProperties[0].key().int_value().value(), |
| parent_1_node_property_1.key.int_value()); |
| ASSERT_TRUE(parent_1_node_property_1.value.is_int_value()); |
| ASSERT_EQ(kParent1NodeProperties[0].value().int_value().value(), |
| parent_1_node_property_1.value.int_value()); |
| |
| // Verify parent 2 properties. |
| const auto& parent_2_node_properties = composite->GetNodeProperties(kParent2Name); |
| ASSERT_TRUE(parent_2_node_properties.has_value()); |
| ASSERT_EQ(1ul, parent_2_node_properties->size()); |
| |
| const auto& parent_2_node_property_1 = parent_2_node_properties.value()[0]; |
| ASSERT_TRUE(parent_2_node_property_1.key.is_string_value()); |
| ASSERT_EQ(kParent2NodeProperties[0].key().string_value().value(), |
| parent_2_node_property_1.key.string_value().get()); |
| ASSERT_TRUE(parent_2_node_property_1.value.is_string_value()); |
| ASSERT_EQ(kParent2NodeProperties[0].value().string_value().value(), |
| parent_2_node_property_1.value.string_value().get()); |
| } |
| |
| // Verify Node::UnbindChildren() unbinds all of the children of a node with zero child. |
| TEST_F(Dfv2NodeTest, UnbindChildrenZeroChildren) { |
| auto parent = CreateNode("parent"); |
| auto device_controller = ConnectToDeviceController(parent); |
| |
| ASSERT_TRUE(parent->children().empty()); |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [](auto& result) { ASSERT_EQ(result.status(), ZX_OK); }); |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| ASSERT_TRUE(parent->children().empty()); |
| } |
| |
| // Verify Node::UnbindChildren() unbinds all of the children of a node with one child. |
| TEST_F(Dfv2NodeTest, UnbindChildrenOneChild) { |
| auto parent = CreateNode("parent"); |
| auto child = CreateNode("child", parent); |
| auto device_controller = ConnectToDeviceController(parent); |
| |
| ASSERT_EQ(parent->children().size(), 1u); |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [](auto& result) { ASSERT_EQ(result.status(), ZX_OK); }); |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| ASSERT_TRUE(parent->children().empty()); |
| } |
| |
| // Verify Node::UnbindChildren() unbinds all of the children of a node with one child that has a |
| // driver bound to it. |
| TEST_F(Dfv2NodeTest, UnbindChildrenOneBoundChild) { |
| const std::string kChildNodeName = "child"; |
| |
| auto parent = CreateNode("parent"); |
| auto child = CreateNode(kChildNodeName, parent); |
| StartTestDriver(child); |
| ASSERT_TRUE(child->HasDriverComponent()); |
| auto device_controller = ConnectToDeviceController(parent); |
| |
| // Get the driver so that the test can properly close the driver's connection when the driver |
| // receives a Stop fidl request. |
| auto [driver_server, node_client] = node_manager->driver_host().TakeDriver(kChildNodeName); |
| FakeDriver driver{dispatcher(), std::move(driver_server), std::move(node_client)}; |
| |
| ASSERT_EQ(parent->children().size(), 1u); |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [](auto& result) { ASSERT_EQ(result.status(), ZX_OK); }); |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| ASSERT_TRUE(parent->children().empty()); |
| } |
| |
| // Verify Node::UnbindChildren() unbinds all of the children of a node with four children. |
| TEST_F(Dfv2NodeTest, UnbindChildrenFourChildren) { |
| const size_t kNumChildren = 4; |
| |
| auto parent = CreateNode("parent"); |
| std::vector<std::shared_ptr<driver_manager::Node>> children; |
| for (size_t i = 0; i < kNumChildren; ++i) { |
| children.emplace_back(CreateNode("child-" + std::to_string(i), parent)); |
| } |
| auto device_controller = ConnectToDeviceController(parent); |
| |
| ASSERT_EQ(parent->children().size(), kNumChildren); |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [](auto& result) { ASSERT_EQ(result.status(), ZX_OK); }); |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| ASSERT_TRUE(parent->children().empty()); |
| } |
| |
| // Verify that multiple requests to Node::UnbindChildren() will succeed. Both of these requests are |
| // sent before the node can complete either. |
| TEST_F(Dfv2NodeTest, UnbindChildrenMultipleCalls) { |
| auto parent = CreateNode("parent"); |
| auto child = CreateNode("child", parent); |
| auto device_controller = ConnectToDeviceController(parent); |
| |
| ASSERT_EQ(parent->children().size(), 1u); |
| size_t unbind_children_complete_count = 0; |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [&unbind_children_complete_count](auto& result) { |
| ASSERT_EQ(result.status(), ZX_OK); |
| unbind_children_complete_count += 1; |
| }); |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [&unbind_children_complete_count](auto& result) { |
| ASSERT_EQ(result.status(), ZX_OK); |
| unbind_children_complete_count += 1; |
| }); |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| ASSERT_EQ(unbind_children_complete_count, 2u); |
| ASSERT_TRUE(parent->children().empty()); |
| } |
| |
| // Verify that Node::AddChild() fails when a node is in the middle of unbinding children. |
| // TODO(https://fxbug.dev/333783189): Re-enable flaky test case. |
| TEST_F(Dfv2NodeTest, DISABLED_UnbindChildrenFailAddChild) { |
| const std::string kChildNode1Name = "child-1"; |
| |
| auto parent = CreateNode("parent"); |
| auto child_1 = CreateNode(kChildNode1Name, parent); |
| StartTestDriver(child_1); |
| ASSERT_TRUE(child_1->HasDriverComponent()); |
| auto device_controller = ConnectToDeviceController(parent); |
| |
| // Get the driver so that the test can prevent Node::UnbindChildren() from fully completing by |
| // pausing TestDriver::Stop(). The driver will live on a separate thread in order to not block the |
| // main thread while the driver waits to complete stopping. |
| async::Loop driver_loop{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| ASSERT_EQ(driver_loop.StartThread("driver"), ZX_OK); |
| auto [driver_server, node_client] = node_manager->driver_host().TakeDriver(kChildNode1Name); |
| auto complete_stop = std::make_shared<libsync::Completion>(); |
| async_patterns::TestDispatcherBound<FakeDriver> driver{ |
| driver_loop.dispatcher(), std::in_place, |
| async_patterns::PassDispatcher, std::move(driver_server), |
| std::move(node_client), [complete_stop]() { |
| complete_stop->Wait(); }}; |
| |
| ASSERT_EQ(parent->children().size(), 1u); |
| device_controller->UnbindChildren().ThenExactlyOnce( |
| [](auto& result) { ASSERT_EQ(result.status(), ZX_OK); }); |
| |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| // At this point, the node has received the UnbindChildren request and is waiting for child-1's |
| // driver to fully stop. |
| |
| // Fail to add a second child. |
| zx::result node_controller_endpoints = |
| fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>(); |
| ASSERT_EQ(node_controller_endpoints.status_value(), ZX_OK); |
| zx::result node_endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::Node>(); |
| ASSERT_EQ(node_endpoints.status_value(), ZX_OK); |
| fuchsia_driver_framework::NodeAddArgs args{{.name = "child-2"}}; |
| parent->AddChild(std::move(args), std::move(node_controller_endpoints->server), |
| std::move(node_endpoints->server), |
| [](fit::result<fuchsia_driver_framework::wire::NodeError, |
| std::shared_ptr<driver_manager::Node>> |
| result) { |
| ASSERT_TRUE(result.is_error()); |
| ASSERT_EQ( |
| result.error_value(), |
| fuchsia_driver_framework::wire::NodeError::kUnbindChildrenInProgress); |
| }); |
| |
| // Let the driver complete stopping. |
| complete_stop->Signal(); |
| // Wait for the driver to complete stopping. |
| driver.SyncCall([](FakeDriver* driver) {}); |
| |
| // Let Node::UnbindChildren() complete. |
| ASSERT_TRUE(RunLoopUntilIdle()); |
| |
| ASSERT_TRUE(parent->children().empty()); |
| } |
| |
| // Verify `Node::ScheduleUnbind` will unbind a node that is bound to a driver. |
| TEST_F(Dfv2NodeTest, ScheduleUnbind) { |
| const std::string kNodeName = "test"; |
| |
| auto node = CreateNode(kNodeName); |
| StartTestDriver(node); |
| ASSERT_TRUE(node->HasDriverComponent()); |
| |
| // Get the driver so that the test can properly close the driver's connection when the driver |
| // receives a Stop fidl request. |
| auto [driver_server, node_client] = node_manager->driver_host().TakeDriver(kNodeName); |
| FakeDriver driver{dispatcher(), std::move(driver_server), std::move(node_client)}; |
| |
| auto device_controller = ConnectToDeviceController(node); |
| device_controller->ScheduleUnbind().ThenExactlyOnce( |
| [](auto& result) { ASSERT_EQ(result.status(), ZX_OK); }); |
| RunLoopUntilIdle(); |
| ASSERT_FALSE(node->HasDriverComponent()); |
| } |