blob: da8a3f3938568a14b44d32face9ef67317246cad [file] [log] [blame]
// 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/driver_host.h"
#include "src/devices/bin/driver_manager/shutdown/node_removal_tracker.h"
#include "src/devices/bin/driver_manager/tests/driver_manager_test_base.h"
using namespace driver_manager;
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 {
ZX_ASSERT(false);
}
void CreateChild(CreateChildRequestView request, CreateChildCompleter::Sync& completer) override {
ZX_ASSERT(false);
}
void DestroyChild(DestroyChildRequestView request,
DestroyChildCompleter::Sync& completer) override {
destroy_completers_[std::string(request->child.name.data(), request->child.name.size())] =
completer.ToAsync();
}
void ListChildren(ListChildrenRequestView request,
ListChildrenCompleter::Sync& completer) override {
ZX_ASSERT(false);
}
void ReplyDestroyChildRequest(std::string child_moniker) {
ASSERT_TRUE(destroy_completers_[child_moniker].has_value());
destroy_completers_[child_moniker]->ReplySuccess();
destroy_completers_[child_moniker].reset();
}
private:
async_dispatcher_t* dispatcher_;
std::unordered_map<std::string, std::optional<DestroyChildCompleter::Async>> destroy_completers_;
};
class FakeDriverHost : public 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);
if (should_queue_start_callback_) {
start_callbacks_[node_name] = std::move(cb);
return;
}
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();
}
void InvokeStartCallback(std::string node_name, zx::result<> result) {
start_callbacks_[node_name](result);
start_callbacks_.erase(node_name);
}
void set_should_queue_start_callback(bool should_queue) {
should_queue_start_callback_ = should_queue;
}
private:
bool should_queue_start_callback_ = false;
std::unordered_map<std::string, StartCallback> start_callbacks_;
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<DriverHost*> CreateDriverHost(bool use_next_vdso) override {
return zx::ok(&driver_host_);
}
void DestroyDriverComponent(
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); }
void AddClient(const std::string& node_name,
fidl::ClientEnd<fuchsia_component_runner::ComponentController> client) {
clients_[node_name] = std::move(client);
}
FakeDriverHost& driver_host() { return driver_host_; }
private:
fidl::WireClient<fuchsia_component::Realm> realm_;
std::unordered_map<std::string, fidl::ClientEnd<fuchsia_component_runner::ComponentController>>
clients_;
FakeDriverHost driver_host_;
};
class NodeShutdownTest : public DriverManagerTestBase {
public:
void SetUp() override {
DriverManagerTestBase::SetUp();
realm_ = std::make_unique<TestRealm>(dispatcher());
node_manager = std::make_unique<FakeNodeManager>(
fidl::WireClient<fuchsia_component::Realm>(realm_->Connect(), dispatcher()));
removal_tracker_ = std::make_unique<NodeRemovalTracker>(dispatcher());
removal_tracker_->set_all_callback([this]() { remove_all_callback_invoked_ = true; });
removal_tracker_->set_pkg_callback([this]() { remove_pkg_callback_invoked_ = true; });
nodes_["root"] = root();
}
void StartDriver(std::string node_name) {
ASSERT_NE(nodes_.find(node_name), nodes_.end());
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")),
}},
};
auto [_, server_end] = fidl::Endpoints<fuchsia_io::Directory>::Create();
auto start_info = fuchsia_component_runner::ComponentStartInfo{{
.resolved_url = node_name,
.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();
auto node = nodes_[node_name].lock();
ASSERT_TRUE(node);
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); });
RunLoopUntilIdle();
}
void AddNode(std::string node) { AddChildNode("root", node); }
void AddNodeAndStartDriver(std::string node) { AddNodeAndStartDriver(node, std::nullopt); }
void AddNodeAndStartDriver(std::string node, std::optional<Collection> collection) {
AddChildNodeAndStartDriver("root", node, collection);
}
void AddChildNode(std::string parent_name, std::string child_name) {
AddChildNode(parent_name, child_name, std::nullopt);
}
void AddChildNode(std::string parent_name, std::string child_name,
std::optional<Collection> collection) {
// This function should only be called for a new node.
ASSERT_EQ(nodes_.find(child_name), nodes_.end());
ASSERT_NE(nodes_.find(parent_name), nodes_.end());
// For testing purposes, the parent should not contain the children with the same node names.
auto parent = nodes_[parent_name].lock();
ASSERT_TRUE(parent);
for (auto child : parent->children()) {
ASSERT_NE(child->name(), child_name);
}
std::shared_ptr<Node> child =
DriverManagerTestBase::CreateNode(child_name, nodes_[parent_name]);
if (collection.has_value()) {
child->set_collection(collection.value());
}
nodes_[child_name] = child;
}
void AddCompositeNode(std::string composite_name, std::vector<std::string> parents) {
ASSERT_EQ(nodes_.find(composite_name), nodes_.end());
std::vector<std::weak_ptr<Node>> parent_nodes;
parent_nodes.reserve(parents.size());
for (auto& parent_name : parents) {
ASSERT_NE(nodes_.find(parent_name), nodes_.end());
parent_nodes.push_back(nodes_[parent_name]);
}
std::vector<fuchsia_driver_framework::NodePropertyEntry> parent_properties(parents.size());
nodes_[composite_name] = CreateCompositeNode(composite_name, parent_nodes, parent_properties,
/* primary_index */ 0);
}
std::shared_ptr<Node> GetNode(std::string node_name) { return nodes_[node_name].lock(); }
void AddChildNodeAndStartDriver(std::string parent, std::string child) {
AddChildNodeAndStartDriver(parent, child, std::nullopt);
}
void AddChildNodeAndStartDriver(std::string parent, std::string child,
std::optional<Collection> collection) {
AddChildNode(parent, child, collection);
StartDriver(child);
}
void InvokeDestroyChildResponse(std::string node_name) {
auto node = nodes_[node_name].lock();
ASSERT_TRUE(node);
realm_->ReplyDestroyChildRequest(node->MakeComponentMoniker());
RunLoopUntilIdle();
}
void CloseDriverForNode(std::string node_name) {
node_manager->CloseDriverForNode(node_name);
RunLoopUntilIdle();
}
void InvokeRemoveNode(std::string node_name) { InvokeRemoveNode(node_name, RemovalSet::kAll); }
void InvokeRemoveNode(std::string node_name, RemovalSet set) {
auto node = nodes_[node_name].lock();
ASSERT_TRUE(node);
node->Remove(set, removal_tracker_.get());
removal_tracker_->FinishEnumeration();
RunLoopUntilIdle();
}
void VerifyState(std::string node_name, NodeState expected_state) {
RunLoopUntilIdle();
auto node = nodes_[node_name].lock();
ASSERT_TRUE(node);
ASSERT_EQ(expected_state, node->GetShutdownHelper().node_state())
<< "Node: " << node_name
<< " Expected: " << ShutdownHelper::NodeStateAsString(expected_state)
<< " Actual: " << node->GetShutdownHelper().NodeStateAsString();
}
void VerifyStates(std::map<std::string, NodeState> expected_states) {
for (const auto& [node_name, state] : expected_states) {
VerifyState(node_name, state);
}
}
void VerifyNodeRemovedFromParent(std::string node_name, std::string parent_name) {
RunLoopUntilIdle();
ASSERT_FALSE(nodes_[node_name].lock())
<< " node_name: " << node_name << " parent_name: " << parent_name;
if (auto parent = nodes_[parent_name].lock(); parent) {
for (auto child : parent->children()) {
ASSERT_NE(child->name(), node_name);
}
}
}
void VerifyRemovalTrackerPkgCallbackInvoked() { ASSERT_TRUE(remove_pkg_callback_invoked_); }
void VerifyRemovalTrackerPkgCallbackNotInvoked() { ASSERT_FALSE(remove_pkg_callback_invoked_); }
void VerifyRemovalTrackerAllCallbackInvoked() { ASSERT_TRUE(remove_all_callback_invoked_); }
void VerifyRemovalTrackerAllCallbackNotInvoked() { ASSERT_FALSE(remove_all_callback_invoked_); }
protected:
NodeManager* GetNodeManager() override { return node_manager.get(); }
TestRealm* realm() { return realm_.get(); }
std::unique_ptr<FakeNodeManager> node_manager;
private:
std::unique_ptr<NodeRemovalTracker> removal_tracker_;
bool remove_all_callback_invoked_ = false;
bool remove_pkg_callback_invoked_ = false;
std::unordered_map<std::string, std::weak_ptr<Node>> nodes_;
std::unique_ptr<TestRealm> realm_;
};
TEST_F(NodeShutdownTest, BasicRemoveAllNodes) {
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_a");
AddChildNodeAndStartDriver("node_a", "node_a_b");
AddChildNodeAndStartDriver("node_a_b", "node_a_b_a");
InvokeRemoveNode("node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriver},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriverComponent},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
// Close node_a_a's driver component. The node completes shutdown and should be removed.
InvokeDestroyChildResponse("node_a_a");
VerifyNodeRemovedFromParent("node_a_a", "node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriverComponent}});
// Close node_a_b_a's driver component. The node should complete shutdown and be removed.
InvokeDestroyChildResponse("node_a_b_a");
VerifyNodeRemovedFromParent("node_a_b_a", "node_a_b");
VerifyStates(
{{"node_a", NodeState::kWaitingOnChildren}, {"node_a_b", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnDriverComponent}});
InvokeDestroyChildResponse("node_a_b");
VerifyNodeRemovedFromParent("node_a_b", "node_a");
VerifyState("node_a", NodeState::kWaitingOnDriver);
CloseDriverForNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
VerifyRemovalTrackerPkgCallbackInvoked();
}
TEST_F(NodeShutdownTest, RemoveCompositeNode) {
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_a");
AddChildNodeAndStartDriver("node_a", "node_a_b");
AddChildNodeAndStartDriver("node_a", "node_a_c");
AddCompositeNode("composite_abc", {"node_a_a", "node_a_b", "node_a_c"});
StartDriver("composite_abc");
InvokeRemoveNode("node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_c", NodeState::kWaitingOnChildren},
{"composite_abc", NodeState::kWaitingOnDriver}});
CloseDriverForNode("composite_abc");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_c", NodeState::kWaitingOnChildren},
{"composite_abc", NodeState::kWaitingOnDriverComponent}});
InvokeDestroyChildResponse("composite_abc");
VerifyNodeRemovedFromParent("composite_abc", "node_a_a");
VerifyNodeRemovedFromParent("composite_abc", "node_a_b");
VerifyNodeRemovedFromParent("composite_abc", "node_a_c");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriver},
{"node_a_b", NodeState::kWaitingOnDriver},
{"node_a_c", NodeState::kWaitingOnDriver}});
auto remove_nodes = {"node_a_a", "node_a_b", "node_a_c"};
for (auto node : remove_nodes) {
CloseDriverForNode(node);
InvokeDestroyChildResponse(node);
VerifyNodeRemovedFromParent(node, "node_a");
}
VerifyState("node_a", NodeState::kWaitingOnDriver);
CloseDriverForNode("node_a");
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, RemoveLeafNode) {
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_a");
InvokeRemoveNode("node_a_a");
VerifyState("node_a", NodeState::kRunning);
VerifyState("node_a_a", NodeState::kWaitingOnDriver);
CloseDriverForNode("node_a_a");
VerifyState("node_a_a", NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse("node_a_a");
VerifyNodeRemovedFromParent("node_a_a", "node_a");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, RemoveNodeWithNoChildren) {
AddNodeAndStartDriver("node_a");
InvokeRemoveNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriver);
CloseDriverForNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, RemoveNodeWithNoDriverOrChildren) {
AddNode("node_a"); // No driver.
InvokeRemoveNode("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
}
TEST_F(NodeShutdownTest, DriverShutdownWhileWaitingOnChildren) {
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_a");
InvokeRemoveNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnChildren);
VerifyState("node_a_a", NodeState::kWaitingOnDriver);
// Close node_a's while it's still waiting for node_a_a. Node_a should
// still wait for its children.
CloseDriverForNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnChildren);
VerifyState("node_a_a", NodeState::kWaitingOnDriver);
// Close node_a_a's driver.
CloseDriverForNode("node_a_a");
VerifyState("node_a", NodeState::kWaitingOnChildren);
VerifyState("node_a_a", NodeState::kWaitingOnDriverComponent);
// Destroy node_a_a's driver component. Since node_a's driver was already
// closed, it should go straight to destroying the driver component.
InvokeDestroyChildResponse("node_a_a");
VerifyNodeRemovedFromParent("node_a_a", "node_a");
VerifyState("node_a", NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, RemoveAfterBindFailure) {
AddNodeAndStartDriver("node_a");
GetNode("node_a")->CompleteBind(zx::error(ZX_ERR_NOT_FOUND));
InvokeRemoveNode("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
}
TEST_F(NodeShutdownTest, WaitBindBeforeShutdown) {
node_manager->driver_host().set_should_queue_start_callback(true);
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_b");
AddChildNodeAndStartDriver("node_a_b", "node_a_b_a");
// Complete bind successfully for node_a.
node_manager->driver_host().InvokeStartCallback("node_a", zx::ok());
InvokeRemoveNode("node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnDriverBind},
{"node_a_b_a", NodeState::kWaitingOnDriverBind}});
node_manager->driver_host().InvokeStartCallback("node_a_b", zx::ok());
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriverBind}});
node_manager->driver_host().InvokeStartCallback("node_a_b_a", zx::ok());
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b_a");
InvokeDestroyChildResponse("node_a_b_a");
VerifyNodeRemovedFromParent("node_a_b_a", "node_a_b");
CloseDriverForNode("node_a_b");
InvokeDestroyChildResponse("node_a_b");
VerifyNodeRemovedFromParent("node_a_b", "node_a");
CloseDriverForNode("node_a");
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, WaitBindBeforeShutdownForPkgNode) {
const char* node_pkg1 = "node_package1";
const char* node_pkg2 = "node_package2";
const char* node_boot = "node_boot";
node_manager->driver_host().set_should_queue_start_callback(true);
AddNodeAndStartDriver(node_boot, Collection::kBoot);
AddChildNodeAndStartDriver(node_boot, node_pkg1, Collection::kPackage);
AddChildNodeAndStartDriver(node_boot, node_pkg2, Collection::kPackage);
// Complete bind successfully for boot node.
node_manager->driver_host().InvokeStartCallback(node_boot, zx::ok());
node_manager->driver_host().InvokeStartCallback(node_pkg1, zx::ok());
InvokeRemoveNode(node_boot, RemovalSet::kPackage);
VerifyStates({{node_boot, NodeState::kPrestop},
{node_pkg1, NodeState::kWaitingOnDriver},
{node_pkg2, NodeState::kWaitingOnDriverBind}});
CloseDriverForNode(node_pkg1);
InvokeDestroyChildResponse(node_pkg1);
VerifyNodeRemovedFromParent(node_pkg1, node_boot);
node_manager->driver_host().InvokeStartCallback(node_pkg2, zx::ok());
VerifyStates({{node_boot, NodeState::kPrestop}, {node_pkg2, NodeState::kWaitingOnDriver}});
CloseDriverForNode(node_pkg2);
InvokeDestroyChildResponse(node_pkg2);
VerifyNodeRemovedFromParent(node_pkg2, node_boot);
VerifyState(node_boot, NodeState::kPrestop);
VerifyRemovalTrackerPkgCallbackInvoked();
}
TEST_F(NodeShutdownTest, BindFailureDuringRemove) {
AddNodeAndStartDriver("node_a");
InvokeRemoveNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriver);
GetNode("node_a")->CompleteBind(zx::error(ZX_ERR_NOT_FOUND));
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, DriverHostFailure) {
node_manager->driver_host().set_should_queue_start_callback(true);
AddNodeAndStartDriver("node_a");
node_manager->driver_host().InvokeStartCallback("node_a", zx::error(ZX_ERR_INTERNAL));
InvokeRemoveNode("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
}
TEST_F(NodeShutdownTest, RemoveDuringDriverHostStartWithFailure) {
node_manager->driver_host().set_should_queue_start_callback(true);
AddNodeAndStartDriver("node_a");
InvokeRemoveNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriverBind);
node_manager->driver_host().InvokeStartCallback("node_a", zx::error(ZX_ERR_INTERNAL));
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, OverlappingRemoveCalls) {
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_a");
AddChildNodeAndStartDriver("node_a", "node_a_b");
AddChildNodeAndStartDriver("node_a_b", "node_a_b_a");
InvokeRemoveNode("node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriver},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriverComponent},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
InvokeRemoveNode("node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriverComponent},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
// Close node_a_a's driver component. The node completes shutdown and should be removed.
InvokeDestroyChildResponse("node_a_a");
VerifyNodeRemovedFromParent("node_a_a", "node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriverComponent}});
// Close node_a_b_a's driver component. The node should complete shutdown and be removed.
InvokeDestroyChildResponse("node_a_b_a");
VerifyNodeRemovedFromParent("node_a_b_a", "node_a_b");
VerifyStates(
{{"node_a", NodeState::kWaitingOnChildren}, {"node_a_b", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnDriverComponent}});
InvokeDestroyChildResponse("node_a_b");
VerifyNodeRemovedFromParent("node_a_b", "node_a");
VerifyState("node_a", NodeState::kWaitingOnDriver);
CloseDriverForNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
TEST_F(NodeShutdownTest, OverlappingRemoveCalls_DifferentNodes) {
AddNodeAndStartDriver("node_a");
AddChildNodeAndStartDriver("node_a", "node_a_a");
AddChildNodeAndStartDriver("node_a", "node_a_b");
AddChildNodeAndStartDriver("node_a_b", "node_a_b_a");
InvokeRemoveNode("node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriver},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_a", NodeState::kWaitingOnDriverComponent},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
// Close node_a_a's driver component. The node completes shutdown and should be removed.
InvokeDestroyChildResponse("node_a_a");
VerifyNodeRemovedFromParent("node_a_a", "node_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
InvokeRemoveNode("node_a_b");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b_a");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnChildren},
{"node_a_b_a", NodeState::kWaitingOnDriverComponent}});
// Close node_a_b_a's driver component. The node should complete shutdown and be removed.
InvokeDestroyChildResponse("node_a_b_a");
VerifyNodeRemovedFromParent("node_a_b_a", "node_a_b");
VerifyStates(
{{"node_a", NodeState::kWaitingOnChildren}, {"node_a_b", NodeState::kWaitingOnDriver}});
CloseDriverForNode("node_a_b");
VerifyStates({{"node_a", NodeState::kWaitingOnChildren},
{"node_a_b", NodeState::kWaitingOnDriverComponent}});
InvokeDestroyChildResponse("node_a_b");
VerifyNodeRemovedFromParent("node_a_b", "node_a");
VerifyState("node_a", NodeState::kWaitingOnDriver);
CloseDriverForNode("node_a");
VerifyState("node_a", NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse("node_a");
VerifyNodeRemovedFromParent("node_a", "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
// Test nodes spread across boot and package collections when we first just
// stop the package drivers and then ask to stop the boot drivers.
TEST_F(NodeShutdownTest, NodesInDifferentCollections) {
// TEST OUTLINE
// Create a root and attach to it three children. The first and third are
// package drivers, the second is a boot driver. This arrangement is
// intentional because the bug this test was written in response to only
// happened when a boot driver child was first and a package driver child
// after it as children were process LIFO. We put a boot driver in the middle
// in case we reintroduce the bug *and* switch to FIFO processing of nodes.
const char* root_name = "node_root";
const char* node_pkg1 = "node_package1";
const char* node_pkg2 = "node_package2";
const char* node_boot = "node_boot";
// Make the root and put it in the boot collection. We must have the root
// in boot because if it were in package it would not matter if one of its
// children were a boot driver.
AddNodeAndStartDriver(root_name, Collection::kBoot);
AddChildNodeAndStartDriver(root_name, node_pkg1, Collection::kPackage);
AddChildNodeAndStartDriver(root_name, node_boot, Collection::kBoot);
AddChildNodeAndStartDriver(root_name, node_pkg2, Collection::kPackage);
// Make the call to remove package-based drivers. This should *NOT* stopt the
// boot drivers, but instead put them in a pre-stop state.
InvokeRemoveNode(root_name, RemovalSet::kPackage);
VerifyStates({{root_name, NodeState::kPrestop},
{node_boot, NodeState::kPrestop},
{node_pkg1, NodeState::kWaitingOnDriver},
{node_pkg2, NodeState::kWaitingOnDriver}});
// Stop the drivers and components backing the package driver nodes.
CloseDriverForNode(node_pkg1);
InvokeDestroyChildResponse(node_pkg1);
CloseDriverForNode(node_pkg2);
InvokeDestroyChildResponse(node_pkg2);
// Check these children are gone
VerifyNodeRemovedFromParent(node_pkg1, root_name);
VerifyNodeRemovedFromParent(node_pkg2, root_name);
// Check remaining nodes are in expected state.
VerifyStates({
{root_name, NodeState::kPrestop},
{node_boot, NodeState::kPrestop},
});
VerifyRemovalTrackerPkgCallbackInvoked();
VerifyRemovalTrackerAllCallbackNotInvoked();
// Now remove all drivers.
InvokeRemoveNode(root_name, RemovalSet::kAll);
VerifyStates({
{root_name, NodeState::kWaitingOnChildren},
{node_boot, NodeState::kWaitingOnDriver},
});
// Take the child of the test root through its stages.
CloseDriverForNode(node_boot);
VerifyState(node_boot, NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse(node_boot);
// Check the child was removed from the parent.
VerifyNodeRemovedFromParent(node_boot, root_name);
// Now test root just should be waiting on its driver, take it down the rest
// of the way.
VerifyState(root_name, NodeState::kWaitingOnDriver);
CloseDriverForNode(root_name);
VerifyState(root_name, NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse(root_name);
// Check the test root was removed from the realm root.
VerifyNodeRemovedFromParent(root_name, "root");
VerifyRemovalTrackerAllCallbackInvoked();
}
// Test behavior with nodes in boot and package sets when we shut down all
// drivers.
TEST_F(NodeShutdownTest, RemoveAllRemovesEverything) {
// TEST OUTLINE
// Create a test root node, then create three children. Two of those children
// will be package drivers and one will be a boot driver. The root node is a
// boot driver as well. We expect that when we issue a shutdown for
// RemovalSet::kAll that they all get torn down.
const char* root_name = "node_root";
const char* node_pkg1 = "node_package1";
const char* node_pkg2 = "node_package2";
const char* node_boot = "node_boot";
// Make the root and put it in the boot collection. We must have the root
// in boot because if it were in package it would not matter if one of its
// children were a boot driver.
AddNodeAndStartDriver(root_name, Collection::kBoot);
AddChildNodeAndStartDriver(root_name, node_pkg1, Collection::kPackage);
AddChildNodeAndStartDriver(root_name, node_boot, Collection::kBoot);
AddChildNodeAndStartDriver(root_name, node_pkg2, Collection::kPackage);
// Make the call to remove package-based drivers. This should *NOT* stop the
// boot drivers, but instead put them in a pre-stop state.
InvokeRemoveNode(root_name, RemovalSet::kAll);
VerifyStates({{root_name, NodeState::kWaitingOnChildren},
{node_boot, NodeState::kWaitingOnDriver},
{node_pkg1, NodeState::kWaitingOnDriver},
{node_pkg2, NodeState::kWaitingOnDriver}});
// Stop the drivers and components backing the package driver nodes.
CloseDriverForNode(node_pkg1);
InvokeDestroyChildResponse(node_pkg1);
CloseDriverForNode(node_pkg2);
InvokeDestroyChildResponse(node_pkg2);
// Check these children are gone.
VerifyNodeRemovedFromParent(node_pkg1, root_name);
VerifyNodeRemovedFromParent(node_pkg2, root_name);
VerifyRemovalTrackerPkgCallbackInvoked();
VerifyRemovalTrackerAllCallbackNotInvoked();
// Take the child of the test root through its stages.
CloseDriverForNode(node_boot);
VerifyState(node_boot, NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse(node_boot);
// Check the child was removed from the parent.
VerifyNodeRemovedFromParent(node_boot, root_name);
// Now test root just should be waiting on its driver, take it down the rest
// of the way.
VerifyState(root_name, NodeState::kWaitingOnDriver);
CloseDriverForNode(root_name);
VerifyState(root_name, NodeState::kWaitingOnDriverComponent);
InvokeDestroyChildResponse(root_name);
// Check the test root was removed from the realm root.
VerifyNodeRemovedFromParent(root_name, "root");
VerifyRemovalTrackerAllCallbackInvoked();
}