blob: dbc5a9ed1a950b83e44fc90fe108e672f4e106dc [file]
// Copyright 2026 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/pending_node_manager.h"
#include <fidl/fuchsia.driver.framework/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/driver/component/cpp/node_add_args.h>
#include <gtest/gtest.h>
#include "src/devices/bin/driver_manager/node.h"
#include "src/devices/bin/driver_manager/resource.h"
#include "src/devices/bin/driver_manager/testing/fake_driver_index.h"
#include "src/devices/bin/driver_manager/tests/driver_manager_test_base.h"
namespace fdfw = fuchsia_driver_framework;
using namespace driver_manager;
fdfw::NodeProperty2 MakeProperty(std::string key, std::string value) {
return fdfw::NodeProperty2(key, fdfw::NodePropertyValue::WithStringValue(value));
}
fdfw::ResourceProperty MakeResourceProperty(std::string key, std::string value) {
return fdfw::ResourceProperty(key, fdfw::ResourcePropertyValue::WithStringValue(value));
}
class FakeNodeManager : public TestNodeManagerBase {
public:
FakeNodeManager(fidl::ClientEnd<fuchsia_driver_index::DriverIndex> driver_index)
: driver_index_(std::move(driver_index), async_get_default_dispatcher()) {}
void RequestMatchPendingNode(
fidl::VectorView<fuchsia_driver_framework::wire::ParentSpec2> dependencies,
fit::callback<
void(fidl::WireUnownedResult<fuchsia_driver_index::DriverIndex::MatchPendingNode>&)>
match_callback) override {
driver_index_->MatchPendingNode(dependencies).Then(std::move(match_callback));
}
zx::result<> StartDriver(Node& node, std::string_view url,
fuchsia_driver_framework::DriverPackageType package_type) override {
start_drivers_.push_back({
.name = node.name(),
.url = std::string(url),
});
return zx::ok();
}
struct StartDriverCall {
std::string name;
std::string url;
};
fidl::WireClient<fuchsia_driver_index::DriverIndex> driver_index_;
std::vector<StartDriverCall> start_drivers_;
};
class PendingNodeManagerTest : public DriverManagerTestBase {
public:
PendingNodeManagerTest() {
fake_driver_index_.emplace(
dispatcher(),
[](fuchsia_driver_index::wire::MatchDriverArgs args) {
return zx::error(ZX_ERR_NOT_FOUND);
},
[this](fidl::AnyArena& arena,
fidl::VectorView<fuchsia_driver_framework::wire::ParentSpec2> dependencies)
-> zx::result<fdfw::wire::CompositeDriverMatch> {
if (match_pending_node_result_.has_value()) {
return zx::ok(fidl::ToWire(arena, match_pending_node_result_.value()));
}
return zx::error(ZX_ERR_NOT_FOUND);
});
node_manager_ = std::make_unique<FakeNodeManager>(fake_driver_index_->Connect());
}
NodeManager* GetNodeManager() override { return node_manager_.get(); }
void SetUp() override {
DriverManagerTestBase::SetUp();
pending_node_manager_.emplace(GetNodeManager(), dispatcher());
}
void TearDown() override {
pending_node_manager_.reset();
DriverManagerTestBase::TearDown();
node_manager_.reset();
fake_driver_index_.reset();
}
protected:
std::optional<FakeDriverIndex> fake_driver_index_;
std::unique_ptr<FakeNodeManager> node_manager_;
std::optional<PendingNodeManager> pending_node_manager_;
std::optional<fdfw::CompositeDriverMatch> match_pending_node_result_;
};
TEST_F(PendingNodeManagerTest, AddNodeAndResolveSuccessfully) {
// 1. Setup mock match pending node result.
fdfw::DriverInfo driver_info;
driver_info.url("fuchsia-boot:///#meta/test_composite.cm");
fdfw::CompositeDriverInfo composite_driver;
composite_driver.composite_name("test_composite");
composite_driver.driver_info(std::move(driver_info));
fdfw::CompositeDriverMatch match;
match.composite_driver(std::move(composite_driver));
match.parent_names({{"parent_1"}});
match_pending_node_result_ = std::move(match);
// 2. Add pending node.
fdfw::Selector selector;
std::vector<fdfw::ResourceProperty> include_props;
include_props.push_back(MakeResourceProperty("key_1", "val_1"));
selector.include_properties(std::move(include_props));
fdfw::Dependency dep;
dep.selector(std::move(selector));
fdfw::Node2 node;
node.name("composite_node");
std::vector<fdfw::Dependency> deps;
deps.push_back(std::move(dep));
node.dependencies(std::move(deps));
auto controller = fidl::Endpoints<fdfw::NodeController>::Create();
auto add_result = pending_node_manager_->AddNode(std::move(node), std::move(controller.server),
fidl::ServerEnd<fdfw::Node>{});
ASSERT_TRUE(add_result.is_ok());
// Wait for the async driver index match response.
RunLoopUntilIdle();
// Verify node is matched and pending.
ASSERT_EQ(pending_node_manager_->pending_nodes().size(), 1u);
auto& pending = pending_node_manager_->pending_nodes()[0];
ASSERT_TRUE(pending->matched_driver.has_value());
ASSERT_EQ(pending->matched_driver->composite_driver()->driver_info()->url().value(),
"fuchsia-boot:///#meta/test_composite.cm");
// 3. Resolve the dependencies.
// Create resource satisfying dependency properties.
auto owner = CreateNode("owner");
std::vector<fdfw::NodeProperty2> properties = {
MakeProperty("key_1", "val_1"),
};
auto resource = std::make_shared<Resource>(GetNodeManager()->GetNextResourceId(), owner,
"my_resource", std::move(properties),
std::vector<NodeOffer>{}, std::nullopt, dispatcher());
// Trigger resolution.
std::unordered_map<ResourceId, std::weak_ptr<Resource>> multibind_resources;
multibind_resources[resource->id()] = resource;
pending_node_manager_->TryResolvePendingNodes(multibind_resources);
// Run loop to instantiate node.
RunLoopUntilIdle();
// Node should be resolved and removed from pending.
EXPECT_TRUE(pending_node_manager_->pending_nodes().empty());
// Verify that the driver has been started.
ASSERT_EQ(node_manager_->start_drivers_.size(), 1u);
EXPECT_EQ(node_manager_->start_drivers_[0].name, "composite_node");
EXPECT_EQ(node_manager_->start_drivers_[0].url, "fuchsia-boot:///#meta/test_composite.cm");
}
TEST_F(PendingNodeManagerTest, MatchPendingNodesWithoutDriver) {
// 1. Add pending node. No match returned yet.
fdfw::Selector selector;
std::vector<fdfw::ResourceProperty> include_props;
include_props.push_back(MakeResourceProperty("key_1", "val_1"));
selector.include_properties(std::move(include_props));
fdfw::Dependency dep;
dep.selector(std::move(selector));
fdfw::Node2 node;
node.name("composite_node");
std::vector<fdfw::Dependency> deps;
deps.push_back(std::move(dep));
node.dependencies(std::move(deps));
auto controller = fidl::Endpoints<fdfw::NodeController>::Create();
auto node_ref = fidl::Endpoints<fdfw::Node>::Create();
auto add_result = pending_node_manager_->AddNode(std::move(node), std::move(controller.server),
std::move(node_ref.server));
ASSERT_TRUE(add_result.is_ok());
RunLoopUntilIdle();
// Verify node is pending but not matched to a driver.
ASSERT_EQ(pending_node_manager_->pending_nodes().size(), 1u);
auto& pending = pending_node_manager_->pending_nodes()[0];
ASSERT_FALSE(pending->matched_driver.has_value());
// 2. Setup mock match pending node result.
fdfw::DriverInfo driver_info;
driver_info.url("fuchsia-boot:///#meta/test_composite.cm");
fdfw::CompositeDriverInfo composite_driver;
composite_driver.composite_name("test_composite");
composite_driver.driver_info(std::move(driver_info));
fdfw::CompositeDriverMatch match;
match.composite_driver(std::move(composite_driver));
match.parent_names({{"parent_1"}});
match_pending_node_result_ = std::move(match);
// 3. Retry matching.
pending_node_manager_->MatchPendingNodesWithoutDriver();
RunLoopUntilIdle();
// Verify node is now matched.
ASSERT_EQ(pending_node_manager_->pending_nodes().size(), 1u);
ASSERT_TRUE(pending->matched_driver.has_value());
EXPECT_EQ(pending->matched_driver->composite_driver()->driver_info()->url().value(),
"fuchsia-boot:///#meta/test_composite.cm");
}
TEST_F(PendingNodeManagerTest, AddNodeAndResolveFailAssembly) {
// 1. Setup mock match pending node result.
fdfw::DriverInfo driver_info;
driver_info.url("fuchsia-boot:///#meta/test_composite.cm");
fdfw::CompositeDriverInfo composite_driver;
composite_driver.composite_name("test_composite");
composite_driver.driver_info(std::move(driver_info));
fdfw::CompositeDriverMatch match;
match.composite_driver(std::move(composite_driver));
match.parent_names({{"parent_1"}});
// Set invalid primary parent index (should be < dependencies.size() which will be 1)
match.primary_parent_index(1);
match_pending_node_result_ = std::move(match);
// 2. Add pending node.
fdfw::Selector selector;
std::vector<fdfw::ResourceProperty> include_props;
include_props.push_back(MakeResourceProperty("key_1", "val_1"));
selector.include_properties(std::move(include_props));
fdfw::Dependency dep;
dep.selector(std::move(selector));
fdfw::Node2 node;
node.name("composite_node");
std::vector<fdfw::Dependency> deps;
deps.push_back(std::move(dep));
node.dependencies(std::move(deps));
auto controller = fidl::Endpoints<fdfw::NodeController>::Create();
auto add_result = pending_node_manager_->AddNode(std::move(node), std::move(controller.server),
fidl::ServerEnd<fdfw::Node>{});
ASSERT_TRUE(add_result.is_ok());
// Wait for the async driver index match response.
RunLoopUntilIdle();
// Verify node is matched and pending.
ASSERT_EQ(pending_node_manager_->pending_nodes().size(), 1u);
auto& pending = pending_node_manager_->pending_nodes()[0];
ASSERT_TRUE(pending->matched_driver.has_value());
ASSERT_EQ(pending->matched_driver->composite_driver()->driver_info()->url().value(),
"fuchsia-boot:///#meta/test_composite.cm");
// 3. Resolve the dependencies.
// Create resource satisfying dependency properties.
auto owner = CreateNode("owner");
std::vector<fdfw::NodeProperty2> properties = {
MakeProperty("key_1", "val_1"),
};
auto resource = std::make_shared<Resource>(GetNodeManager()->GetNextResourceId(), owner,
"my_resource", std::move(properties),
std::vector<NodeOffer>{}, std::nullopt, dispatcher());
// Trigger resolution.
std::unordered_map<ResourceId, std::weak_ptr<Resource>> multibind_resources;
multibind_resources[resource->id()] = resource;
pending_node_manager_->TryResolvePendingNodes(multibind_resources);
// Run loop to instantiate node.
RunLoopUntilIdle();
// Node resolution should fail to assemble (because of invalid primary_parent_index),
// and it should be removed from pending nodes.
EXPECT_TRUE(pending_node_manager_->pending_nodes().empty());
// Verify that the driver has NOT been started.
EXPECT_TRUE(node_manager_->start_drivers_.empty());
}
TEST_F(PendingNodeManagerTest, AddNodeAndResolveNoDriver) {
// 1. Setup mock match pending node result to not match any driver (by not setting
// match_pending_node_result_).
match_pending_node_result_ = std::nullopt;
// 2. Add pending node.
fdfw::Selector selector;
std::vector<fdfw::ResourceProperty> include_props;
include_props.push_back(MakeResourceProperty("key_1", "val_1"));
selector.include_properties(std::move(include_props));
fdfw::Dependency dep;
dep.selector(std::move(selector));
fdfw::Node2 node;
node.name("composite_node");
std::vector<fdfw::Dependency> deps;
deps.push_back(std::move(dep));
node.dependencies(std::move(deps));
auto controller = fidl::Endpoints<fdfw::NodeController>::Create();
auto add_result = pending_node_manager_->AddNode(std::move(node), std::move(controller.server),
fidl::ServerEnd<fdfw::Node>{});
ASSERT_TRUE(add_result.is_ok());
// Wait for the async driver index match response.
RunLoopUntilIdle();
// Verify node is pending and not matched to a driver.
ASSERT_EQ(pending_node_manager_->pending_nodes().size(), 1u);
auto& pending = pending_node_manager_->pending_nodes()[0];
ASSERT_FALSE(pending->matched_driver.has_value());
// 3. Resolve the dependencies.
// Create resource satisfying dependency properties.
auto owner = CreateNode("owner");
std::vector<fdfw::NodeProperty2> properties = {
MakeProperty("key_1", "val_1"),
};
auto resource = std::make_shared<Resource>(GetNodeManager()->GetNextResourceId(), owner,
"my_resource", std::move(properties),
std::vector<NodeOffer>{}, std::nullopt, dispatcher());
// Trigger resolution.
std::unordered_map<ResourceId, std::weak_ptr<Resource>> multibind_resources;
multibind_resources[resource->id()] = resource;
pending_node_manager_->TryResolvePendingNodes(multibind_resources);
// Run loop to instantiate node.
RunLoopUntilIdle();
// Node resolution should fail to assemble (because of no matched driver),
// but it should NOT be removed from pending nodes.
EXPECT_EQ(pending_node_manager_->pending_nodes().size(), 1u);
// Verify that the driver has NOT been started.
EXPECT_TRUE(node_manager_->start_drivers_.empty());
}