blob: 181f98e896cc31b881e1aa654522f815a63b3c88 [file] [log] [blame]
// Copyright 2020 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/v2/driver_runner.h"
#include <fuchsia/component/cpp/fidl_test_base.h>
#include <fuchsia/component/decl/cpp/fidl.h>
#include <fuchsia/driver/host/cpp/fidl_test_base.h>
#include <fuchsia/io/cpp/fidl_test_base.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/cpp/binding_set.h>
#include <lib/fit/defer.h>
#include <lib/inspect/cpp/reader.h>
#include <lib/inspect/testing/cpp/inspect.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <list>
#include "src/devices/bin/driver_manager/fake_driver_index.h"
#include "src/lib/testing/loop_fixture/test_loop_fixture.h"
namespace fdata = fuchsia_data;
namespace fdf = fuchsia::driver::framework;
namespace fdh = fuchsia::driver::host;
namespace fio = fuchsia::io;
namespace fprocess = fuchsia_process;
namespace frunner = fuchsia_component_runner;
namespace fcomponent = fuchsia::component;
namespace fdecl = fuchsia::component::decl;
namespace fcd = fuchsia_component_decl;
using namespace testing;
using namespace inspect::testing;
using namespace dfv2;
class FakeContext : public fpromise::context {
public:
fpromise::executor* executor() const override {
EXPECT_TRUE(false);
return nullptr;
}
fpromise::suspended_task suspend_task() override {
EXPECT_TRUE(false);
return fpromise::suspended_task();
}
};
fidl::AnyTeardownObserver TeardownWatcher(size_t index, std::vector<size_t>& indices) {
return fidl::ObserveTeardown([&indices = indices, index] { indices.emplace_back(index); });
}
class TestRealm : public fcomponent::testing::Realm_TestBase {
public:
using CreateChildHandler = fit::function<void(fdecl::CollectionRef collection, fdecl::Child decl,
std::vector<fdecl::Offer> offers)>;
using OpenExposedDirHandler = fit::function<void(
fdecl::ChildRef child, fidl::InterfaceRequest<fio::Directory> exposed_dir)>;
void SetCreateChildHandler(CreateChildHandler create_child_handler) {
create_child_handler_ = std::move(create_child_handler);
}
void SetOpenExposedDirHandler(OpenExposedDirHandler open_exposed_dir_handler) {
open_exposed_dir_handler_ = std::move(open_exposed_dir_handler);
}
fidl::VectorView<fprocess::wire::HandleInfo> GetHandles() {
return fidl::VectorView<fprocess::wire::HandleInfo>::FromExternal(handles_);
}
private:
void CreateChild(fdecl::CollectionRef collection, fdecl::Child decl,
fcomponent::CreateChildArgs args, CreateChildCallback callback) override {
handles_.clear();
for (auto& info : *args.mutable_numbered_handles()) {
handles_.push_back(fprocess::wire::HandleInfo{
.handle = std::move(info.handle),
.id = info.id,
});
}
create_child_handler_(std::move(collection), std::move(decl),
std::move(*args.mutable_dynamic_offers()));
callback(fcomponent::Realm_CreateChild_Result(fpromise::ok()));
}
void OpenExposedDir(fdecl::ChildRef child, fidl::InterfaceRequest<fio::Directory> exposed_dir,
OpenExposedDirCallback callback) override {
open_exposed_dir_handler_(std::move(child), std::move(exposed_dir));
callback(fcomponent::Realm_OpenExposedDir_Result(fpromise::ok()));
}
void NotImplemented_(const std::string& name) override {
printf("Not implemented: Realm::%s\n", name.data());
}
CreateChildHandler create_child_handler_;
OpenExposedDirHandler open_exposed_dir_handler_;
std::vector<fprocess::wire::HandleInfo> handles_;
};
class TestDirectory : public fio::testing::Directory_TestBase {
public:
using OpenHandler =
fit::function<void(std::string path, fidl::InterfaceRequest<fio::Node> object)>;
TestDirectory(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
void Bind(fidl::InterfaceRequest<fio::Directory> request) {
bindings_.AddBinding(this, std::move(request), dispatcher_);
}
void SetOpenHandler(OpenHandler open_handler) { open_handler_ = std::move(open_handler); }
private:
void Clone(fuchsia::io::OpenFlags flags, fidl::InterfaceRequest<fio::Node> object) override {
EXPECT_EQ(fuchsia::io::OpenFlags::CLONE_SAME_RIGHTS, flags);
fidl::InterfaceRequest<fio::Directory> dir(object.TakeChannel());
Bind(std::move(dir));
}
void Open(fuchsia::io::OpenFlags flags, uint32_t mode, std::string path,
fidl::InterfaceRequest<fio::Node> object) override {
open_handler_(std::move(path), std::move(object));
}
void NotImplemented_(const std::string& name) override {
printf("Not implemented: Directory::%s\n", name.data());
}
async_dispatcher_t* dispatcher_;
fidl::BindingSet<fio::Directory> bindings_;
OpenHandler open_handler_;
};
class TestDriver : public fdh::testing::Driver_TestBase {
public:
TestDriver(fdf::NodePtr node) : node_(std::move(node)) {}
fdf::NodePtr& node() { return node_; }
using StopHandler = fit::function<void()>;
void SetStopHandler(StopHandler handler) { stop_handler_ = std::move(handler); }
void set_close_bindings(fit::function<void()> close) { close_binding_ = std::move(close); }
void close_binding() { close_binding_(); }
void Stop() override { stop_handler_(); }
private:
fit::function<void()> close_binding_;
StopHandler stop_handler_;
fdf::NodePtr node_;
void NotImplemented_(const std::string& name) override {
printf("Not implemented: Driver::%s\n", name.data());
}
};
class TestDriverHost : public fdh::testing::DriverHost_TestBase {
public:
using StartHandler = fit::function<void(fdf::DriverStartArgs start_args,
fidl::InterfaceRequest<fdh::Driver> driver)>;
void SetStartHandler(StartHandler start_handler) { start_handler_ = std::move(start_handler); }
private:
void Start(fdf::DriverStartArgs start_args, fidl::InterfaceRequest<fdh::Driver> driver) override {
start_handler_(std::move(start_args), std::move(driver));
}
void NotImplemented_(const std::string& name) override {
printf("Not implemented: DriverHost::%s\n", name.data());
}
StartHandler start_handler_;
};
class TestTransaction : public fidl::Transaction {
public:
TestTransaction(bool close) : close_(close) {}
private:
std::unique_ptr<Transaction> TakeOwnership() override {
EXPECT_TRUE(false);
return nullptr;
}
zx_status_t Reply(fidl::OutgoingMessage* message, fidl::WriteOptions write_options) override {
EXPECT_TRUE(false);
return ZX_OK;
}
void Close(zx_status_t epitaph) override {
EXPECT_TRUE(close_) << "epitaph: " << zx_status_get_string(epitaph);
}
bool close_;
};
struct Driver {
std::string url;
std::string binary;
bool colocate = false;
bool close = false;
};
class DriverRunnerTest : public gtest::TestLoopFixture {
public:
void SetUp() override {
TestLoopFixture::SetUp();
fidl::InterfaceRequestHandler<fcomponent::Realm> handler = [this](auto request) {
EXPECT_EQ(ZX_OK, realm_binding_.Bind(std::move(request), dispatcher()));
};
ASSERT_EQ(ZX_OK, provider_.context()->outgoing()->AddPublicService(std::move(handler)));
}
protected:
inspect::Inspector& inspector() { return inspector_; }
TestRealm& realm() { return realm_; }
TestDirectory& driver_dir() { return driver_dir_; }
TestDriverHost& driver_host() { return driver_host_; }
fidl::ClientEnd<fuchsia_component::Realm> ConnectToRealm() {
fcomponent::RealmPtr realm;
provider_.ConnectToPublicService(realm.NewRequest(dispatcher()));
return fidl::ClientEnd<fuchsia_component::Realm>(realm.Unbind().TakeChannel());
}
FakeDriverIndex CreateDriverIndex() {
return FakeDriverIndex(dispatcher(), [](auto args) -> zx::status<FakeDriverIndex::MatchResult> {
if (args.name().get() == "second") {
return zx::ok(FakeDriverIndex::MatchResult{
.url = "fuchsia-boot:///#meta/second-driver.cm",
});
} else if (args.name().get() == "part-1") {
return zx::ok(FakeDriverIndex::MatchResult{
.url = "fuchsia-boot:///#meta/composite-driver.cm",
.composite = std::make_optional(FakeDriverIndex::CompositeDriverInfo{
.node_index = 0u,
.num_nodes = 2u,
.node_names = {"one", "two"},
})});
} else if (args.name().get() == "part-2") {
return zx::ok(FakeDriverIndex::MatchResult{
.url = "fuchsia-boot:///#meta/composite-driver.cm",
.composite = std::make_optional(FakeDriverIndex::CompositeDriverInfo{
.node_index = 1u,
.num_nodes = 2u,
.node_names = {"one", "two"},
}),
});
} else {
return zx::error(ZX_ERR_NOT_FOUND);
}
});
}
void StartDriverHost(std::string coll, std::string name) {
realm().SetCreateChildHandler(
[coll, name](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {
EXPECT_EQ(coll, collection.name);
EXPECT_EQ(name, decl.name());
EXPECT_EQ("#meta/driver_host2.cm", decl.url());
});
realm().SetOpenExposedDirHandler([this, coll, name](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ(coll, child.collection.value_or(""));
EXPECT_EQ(name, child.name);
driver_host_dir_.Bind(std::move(exposed_dir));
});
driver_host_dir_.SetOpenHandler([this](std::string path, auto object) {
EXPECT_EQ(fdh::DriverHost::Name_, path);
EXPECT_EQ(ZX_OK, driver_host_binding_.Bind(object.TakeChannel(), dispatcher()));
});
}
void StopDriverComponent(fidl::ClientEnd<frunner::ComponentController> component) {
fidl::WireClient client(std::move(component), dispatcher());
auto stop_result = client->Stop();
ASSERT_EQ(ZX_OK, stop_result.status());
RunLoopUntilIdle();
}
fidl::ClientEnd<frunner::ComponentController> StartDriver(DriverRunner& driver_runner,
Driver driver) {
fidl::Arena arena;
fidl::VectorView<fdata::wire::DictionaryEntry> program_entries(arena, 2);
program_entries[0].key.Set(arena, "binary");
program_entries[0].value = fdata::wire::DictionaryValue::WithStr(arena, driver.binary);
program_entries[1].key.Set(arena, "colocate");
program_entries[1].value =
fdata::wire::DictionaryValue::WithStr(arena, driver.colocate ? "true" : "false");
fdata::wire::Dictionary program(arena);
program.set_entries(arena, std::move(program_entries));
auto outgoing_endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
EXPECT_EQ(ZX_OK, outgoing_endpoints.status_value());
frunner::wire::ComponentStartInfo start_info(arena);
start_info.set_resolved_url(arena, driver.url)
.set_program(arena, std::move(program))
.set_ns(arena)
.set_outgoing_dir(std::move(outgoing_endpoints->server))
.set_numbered_handles(arena, realm().GetHandles());
auto controller_endpoints = fidl::CreateEndpoints<frunner::ComponentController>();
EXPECT_EQ(ZX_OK, controller_endpoints.status_value());
TestTransaction transaction(driver.close);
{
fidl::WireServer<frunner::ComponentRunner>::StartCompleter::Sync completer(&transaction);
fidl::WireRequest<frunner::ComponentRunner::Start> request(
start_info, std::move(controller_endpoints->server));
static_cast<fidl::WireServer<frunner::ComponentRunner>&>(driver_runner)
.Start(&request, completer);
}
RunLoopUntilIdle();
return std::move(controller_endpoints->client);
}
zx::status<fidl::ClientEnd<frunner::ComponentController>> StartRootDriver(
std::string url, DriverRunner& driver_runner) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {
EXPECT_EQ("boot-drivers", collection.name);
EXPECT_EQ("root", decl.name());
EXPECT_EQ("fuchsia-boot:///#meta/root-driver.cm", decl.url());
});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ("boot-drivers", child.collection);
EXPECT_EQ("root", child.name);
driver_dir_.Bind(std::move(exposed_dir));
});
auto start = driver_runner.StartRootDriver(url);
if (start.is_error()) {
return start.take_error();
}
EXPECT_TRUE(RunLoopUntilIdle());
StartDriverHost("driver-hosts", "driver-host-0");
auto controller = StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/root-driver.cm",
.binary = "driver/root-driver.so",
});
return zx::ok(std::move(controller));
}
void Unbind() {
driver_host_binding_.Unbind();
EXPECT_TRUE(RunLoopUntilIdle());
}
TestDriver* BindDriver(fidl::InterfaceRequest<fdh::Driver> request, fdf::NodePtr node) {
auto driver = std::make_unique<TestDriver>(std::move(node));
auto driver_ptr = driver.get();
driver_bindings_.AddBinding(driver_ptr, std::move(request), dispatcher(),
[driver = std::move(driver)](auto) {});
driver_ptr->set_close_bindings(
[this, driver_ptr]() mutable { driver_bindings_.CloseBinding(driver_ptr, ZX_OK); });
driver_ptr->SetStopHandler([driver_ptr]() { driver_ptr->close_binding(); });
return driver_ptr;
}
inspect::Hierarchy Inspect(DriverRunner& driver_runner) {
FakeContext context;
auto inspector = driver_runner.Inspect()(context).take_value();
return inspect::ReadFromInspector(inspector)(context).take_value();
}
private:
TestRealm realm_;
TestDirectory driver_host_dir_{dispatcher()};
TestDirectory driver_dir_{dispatcher()};
TestDriverHost driver_host_;
fidl::Binding<fcomponent::Realm> realm_binding_{&realm_};
fidl::Binding<fdh::DriverHost> driver_host_binding_{&driver_host_};
fidl::BindingSet<fdh::Driver> driver_bindings_;
inspect::Inspector inspector_;
sys::testing::ComponentContextProvider provider_{dispatcher()};
};
// Start the root driver.
TEST_F(DriverRunnerTest, StartRootDriver) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
fdf::NodePtr node;
ASSERT_EQ(ZX_OK, node.Bind(start_args.mutable_node()->TakeChannel()));
BindDriver(std::move(request), std::move(node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver. Make sure that the driver is stopped before the Component is exited.
TEST_F(DriverRunnerTest, StartRootDriver_DriverStopBeforeComponentExit) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
std::vector<size_t> event_order;
auto defer = fit::defer([this] { Unbind(); });
TestDriver* root_node = nullptr;
driver_host().SetStartHandler(
[this, &root_node, &event_order](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
root_node = BindDriver(std::move(request), std::move(node));
root_node->SetStopHandler([&event_order, &root_node]() {
event_order.push_back(0);
root_node->close_binding();
});
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
fidl::WireSharedClient<frunner::ComponentController> root_client(
std::move(*root_driver), dispatcher(), TeardownWatcher(1, event_order));
root_node->node().Unbind();
EXPECT_TRUE(RunLoopUntilIdle());
// Make sure the driver was stopped before we told the component framework the driver was stopped.
EXPECT_THAT(event_order, ElementsAre(0, 1));
}
// Start the root driver, and add a child node owned by the root driver.
TEST_F(DriverRunnerTest, StartRootDriver_AddOwnedChild) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
fdf::NodeControllerPtr node_controller;
fdf::NodePtr second_node;
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()),
second_node.NewRequest(dispatcher()),
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, add a child node, then remove it.
TEST_F(DriverRunnerTest, StartRootDriver_RemoveOwnedChild) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
TestDriver* root_test_driver = nullptr;
fdf::NodePtr second_node;
driver_host().SetStartHandler([this, &root_test_driver, &node_controller, &second_node](
fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()),
second_node.NewRequest(dispatcher()),
[](auto result) { EXPECT_FALSE(result.is_err()); });
root_test_driver = BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
node_controller->Remove();
EXPECT_TRUE(RunLoopUntilIdle());
EXPECT_FALSE(second_node.is_bound());
ASSERT_NE(nullptr, root_test_driver);
EXPECT_TRUE(root_test_driver->node().is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add a child node with an invalid name.
TEST_F(DriverRunnerTest, StartRootDriver_AddOwnedChild_InvalidName) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
TestDriver* root_test_driver = nullptr;
fdf::NodePtr invalid_node;
driver_host().SetStartHandler(
[this, &root_test_driver, &invalid_node](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second.invalid");
fdf::NodeControllerPtr node_controller;
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()),
invalid_node.NewRequest(dispatcher()),
[](auto result) { EXPECT_TRUE(result.is_err()); });
root_test_driver = BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
EXPECT_FALSE(invalid_node.is_bound());
ASSERT_NE(nullptr, root_test_driver);
EXPECT_TRUE(root_test_driver->node().is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add two child nodes with duplicate names.
TEST_F(DriverRunnerTest, StartRootDriver_AddOwnedChild_DuplicateNames) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
TestDriver* root_test_driver = nullptr;
fdf::NodePtr second_node, invalid_node;
driver_host().SetStartHandler([this, &root_test_driver, &second_node, &invalid_node](
fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
fdf::NodeControllerPtr node_controller;
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()),
second_node.NewRequest(dispatcher()),
[](auto result) { EXPECT_FALSE(result.is_err()); });
args.set_name("second");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()),
invalid_node.NewRequest(dispatcher()),
[](auto result) { EXPECT_TRUE(result.is_err()); });
root_test_driver = BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
EXPECT_FALSE(invalid_node.is_bound());
EXPECT_TRUE(second_node.is_bound());
ASSERT_NE(nullptr, root_test_driver);
EXPECT_TRUE(root_test_driver->node().is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add a child node with an offer that is missing a
// source.
TEST_F(DriverRunnerTest, StartRootDriver_AddUnownedChild_OfferMissingSource) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol().set_target_name("fuchsia.package.Renamed")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_TRUE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
ASSERT_FALSE(node_controller.is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add a child node with one offer that has a source
// and another that has a target.
TEST_F(DriverRunnerTest, StartRootDriver_AddUnownedChild_OfferHasRef) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source(std::move(fdecl::Ref().set_self(fdecl::SelfRef())))
.set_source_name("fuchsia.package.Protocol")));
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_target(std::move(fdecl::Ref().set_self(fdecl::SelfRef())))
.set_source_name("fuchsia.package.Protocol")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_TRUE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
ASSERT_FALSE(node_controller.is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add a child node with duplicate symbols. The child
// node is unowned, so if we did not have duplicate symbols, the second driver
// would bind to it.
TEST_F(DriverRunnerTest, StartRootDriver_AddUnownedChild_DuplicateSymbols) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_symbols()->emplace_back(
std::move(fdf::NodeSymbol().set_name("sym").set_address(0xfeed)));
args.mutable_symbols()->emplace_back(
std::move(fdf::NodeSymbol().set_name("sym").set_address(0xf00d)));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_TRUE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
ASSERT_FALSE(node_controller.is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add a child node that has a symbol without an
// address.
TEST_F(DriverRunnerTest, StartRootDriver_AddUnownedChild_SymbolMissingAddress) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_symbols()->emplace_back(std::move(fdf::NodeSymbol().set_name("sym")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_TRUE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
ASSERT_FALSE(node_controller.is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and add a child node that has a symbol without a name.
TEST_F(DriverRunnerTest, StartRootDriver_AddUnownedChild_SymbolMissingName) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_symbols()->emplace_back(std::move(fdf::NodeSymbol().set_address(0xfeed)));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_TRUE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
ASSERT_FALSE(node_controller.is_bound());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and then start a second driver in a new driver host.
TEST_F(DriverRunnerTest, StartSecondDriver_NewDriverHost) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
bool did_bind = false;
node_controller.events().OnBind = [&did_bind] { did_bind = true; };
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
realm().SetCreateChildHandler([](fdecl::CollectionRef collection, fdecl::Child decl,
std::vector<fdecl::Offer> offers) {
EXPECT_EQ("boot-drivers", collection.name);
EXPECT_EQ("root.second", decl.name());
EXPECT_EQ("fuchsia-boot:///#meta/second-driver.cm", decl.url());
EXPECT_EQ(1u, offers.size());
ASSERT_TRUE(offers[0].is_protocol());
auto& protocol = offers[0].protocol();
ASSERT_TRUE(protocol.has_source());
ASSERT_TRUE(protocol.source().is_child());
auto& source_ref = protocol.source().child();
EXPECT_EQ("root", source_ref.name);
EXPECT_EQ("boot-drivers", source_ref.collection.value_or("missing"));
ASSERT_TRUE(protocol.has_source_name());
EXPECT_EQ("fuchsia.package.Protocol", protocol.source_name());
ASSERT_TRUE(protocol.has_target_name());
EXPECT_EQ("fuchsia.package.Renamed", protocol.target_name());
});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ("boot-drivers", child.collection);
EXPECT_EQ("root.second", child.name);
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.Protocol")
.set_target_name("fuchsia.package.Renamed")));
args.mutable_symbols()->emplace_back(
std::move(fdf::NodeSymbol().set_name("sym").set_address(0xfeed)));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
EXPECT_TRUE(did_bind);
driver_host().SetStartHandler([](fdf::DriverStartArgs start_args, auto request) {
EXPECT_FALSE(start_args.has_symbols());
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/second-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and then start a second driver in the same driver
// host.
TEST_F(DriverRunnerTest, StartSecondDriver_SameDriverHost) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
bool did_bind = false;
node_controller.events().OnBind = [&did_bind] { did_bind = true; };
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {
EXPECT_EQ("boot-drivers", collection.name);
EXPECT_EQ("root.second", decl.name());
EXPECT_EQ("fuchsia-boot:///#meta/second-driver.cm", decl.url());
});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ("boot-drivers", child.collection);
EXPECT_EQ("root.second", child.name);
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.Protocol")
.set_target_name("fuchsia.package.Renamed")));
args.mutable_symbols()->emplace_back(
std::move(fdf::NodeSymbol().set_name("sym").set_address(0xfeed)));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
EXPECT_TRUE(did_bind);
driver_host().SetStartHandler([](fdf::DriverStartArgs start_args, auto request) {
auto& symbols = start_args.symbols();
EXPECT_EQ(1u, symbols.size());
EXPECT_EQ("sym", symbols[0].name());
EXPECT_EQ(0xfeedu, symbols[0].address());
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/second-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("true", entries[1].value->str());
});
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
.colocate = true,
});
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and then start a second driver that we match based on
// node properties.
TEST_F(DriverRunnerTest, StartSecondDriver_UseProperties) {
FakeDriverIndex driver_index(
dispatcher(), [](auto args) -> zx::status<FakeDriverIndex::MatchResult> {
if (args.has_properties() && args.properties()[0].key().is_int_value() &&
args.properties()[0].key().int_value() == 0x1985 &&
args.properties()[0].value().is_int_value() &&
args.properties()[0].value().int_value() == 0x2301
&& args.properties()[1].key().is_string_value() &&
args.properties()[1].key().string_value().get() == "fuchsia.driver.framework.dfv2" &&
args.properties()[1].value().is_bool_value() &&
args.properties()[1].value().bool_value()
) {
return zx::ok(FakeDriverIndex::MatchResult{
.url = "fuchsia-boot:///#meta/second-driver.cm",
});
} else {
return zx::error(ZX_ERR_NOT_FOUND);
}
});
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {
EXPECT_EQ("boot-drivers", collection.name);
EXPECT_EQ("root.second", decl.name());
EXPECT_EQ("fuchsia-boot:///#meta/second-driver.cm", decl.url());
});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ("boot-drivers", child.collection);
EXPECT_EQ("root.second", child.name);
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_properties()->emplace_back(
std::move(fdf::NodeProperty()
.set_key(fdf::NodePropertyKey::WithIntValue(0x1985))
.set_value(fdf::NodePropertyValue::WithIntValue(0x2301))));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_host().SetStartHandler([](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/second-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("true", entries[1].value->str());
});
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
.colocate = true,
});
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and then add a child node that does not bind to a
// second driver.
TEST_F(DriverRunnerTest, StartSecondDriver_UnknownNode) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("unknown-node");
fdf::NodeControllerPtr node_controller;
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
StartDriver(driver_runner, {.close = true});
ASSERT_EQ(1u, driver_runner.NumOrphanedNodes());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the root driver, and then add a child node that only binds to a base driver.
TEST_F(DriverRunnerTest, StartSecondDriver_BindOrphanToBaseDriver) {
bool base_drivers_loaded = false;
FakeDriverIndex driver_index(
dispatcher(), [&base_drivers_loaded](auto args) -> zx::status<FakeDriverIndex::MatchResult> {
if (base_drivers_loaded) {
if (args.name().get() == "second") {
return zx::ok(FakeDriverIndex::MatchResult{
.url = "fuchsia-boot:///#meta/second-driver.cm",
});
}
}
return zx::error(ZX_ERR_NOT_FOUND);
});
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/root-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("false", entries[1].value->str());
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_properties()->emplace_back(
std::move(fdf::NodeProperty()
.set_key(fdf::NodePropertyKey::WithStringValue("driver.prop-one"))
.set_value(fdf::NodePropertyValue::WithStringValue("value"))));
fdf::NodeControllerPtr node_controller;
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
// Make sure the node we added was orphaned.
ASSERT_EQ(1u, driver_runner.NumOrphanedNodes());
// Set the handlers for the new driver.
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {
EXPECT_EQ("boot-drivers", collection.name);
EXPECT_EQ("root.second", decl.name());
EXPECT_EQ("fuchsia-boot:///#meta/second-driver.cm", decl.url());
});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ("boot-drivers", child.collection);
EXPECT_EQ("root.second", child.name);
driver_dir().Bind(std::move(exposed_dir));
});
// Tell driver index to return the second driver, and wait for base drivers to load.
base_drivers_loaded = true;
driver_runner.ScheduleBaseDriversBinding();
ASSERT_TRUE(RunLoopUntilIdle());
// See that we don't have an orphan anymore.
ASSERT_EQ(0u, driver_runner.NumOrphanedNodes());
StopDriverComponent(std::move(root_driver.value()));
}
// Start the second driver, and then unbind its associated node.
TEST_F(DriverRunnerTest, StartSecondDriver_UnbindSecondNode) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
TestDriver* second_test_driver = nullptr;
driver_host().SetStartHandler(
[this, &second_test_driver](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr second_node;
EXPECT_EQ(ZX_OK, second_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
second_test_driver = BindDriver(std::move(request), std::move(second_node));
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
// Unbinding the second node stops the driver bound to it.
second_test_driver->node().Unbind();
EXPECT_TRUE(RunLoopUntilIdle());
zx_signals_t signals = 0;
ASSERT_EQ(ZX_OK, second_driver.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(),
&signals));
ASSERT_TRUE(signals & ZX_CHANNEL_PEER_CLOSED);
StopDriverComponent(std::move(root_driver.value()));
}
// Start the second driver, and then close the associated Driver protocol
// channel.
TEST_F(DriverRunnerTest, StartSecondDriver_CloseSecondDriver) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
fdf::NodePtr second_node;
fidl::InterfaceRequest<fdh::Driver> second_request;
driver_host().SetStartHandler(
[this, &second_node, &second_request](fdf::DriverStartArgs start_args, auto request) {
second_request = std::move(request);
EXPECT_EQ(ZX_OK, second_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
// Closing the Driver protocol channel of the second driver causes the driver
// to be stopped.
second_request.TakeChannel();
EXPECT_TRUE(RunLoopUntilIdle());
zx_signals_t signals = 0;
ASSERT_EQ(ZX_OK, second_driver.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(),
&signals));
ASSERT_TRUE(signals & ZX_CHANNEL_PEER_CLOSED);
StopDriverComponent(std::move(root_driver.value()));
}
// Start a chain of drivers, and then unbind the second driver's node.
TEST_F(DriverRunnerTest, StartDriverChain_UnbindSecondNode) {
FakeDriverIndex driver_index(dispatcher(),
[](auto args) -> zx::status<FakeDriverIndex::MatchResult> {
std::string name(args.name().get());
return zx::ok(FakeDriverIndex::MatchResult{
.url = "fuchsia-boot:///#meta/" + name + "-driver.cm",
});
});
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("node-0");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
constexpr size_t kMaxNodes = 10;
fdf::NodePtr second_node;
std::vector<fidl::ClientEnd<frunner::ComponentController>> drivers;
for (size_t i = 1; i <= kMaxNodes; i++) {
driver_host().SetStartHandler(
[this, &second_node, &node_controller, i](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
// Only add a node that a driver will be bound to.
if (i != kMaxNodes) {
fdf::NodeAddArgs args;
args.set_name("node-" + std::to_string(i));
node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
}
auto driver = BindDriver(std::move(request), std::move(node));
if (!second_node.is_bound()) {
second_node = std::move(driver->node());
}
});
StartDriverHost("driver-hosts", "driver-host-" + std::to_string(i));
drivers.emplace_back(StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/node-" +
std::to_string(i - 1) + "-driver.cm",
.binary = "driver/driver.so",
}));
}
// Unbinding the second node stops all drivers bound in the sub-tree, in a
// depth-first order.
std::vector<size_t> indices;
std::vector<fidl::WireSharedClient<frunner::ComponentController>> clients;
for (auto& driver : drivers) {
clients.emplace_back(std::move(driver), dispatcher(),
TeardownWatcher(clients.size() + 1, indices));
}
second_node.Unbind();
EXPECT_TRUE(RunLoopUntilIdle());
EXPECT_THAT(indices, ElementsAre(10, 9, 8, 7, 6, 5, 4, 3, 2, 1));
StopDriverComponent(std::move(root_driver.value()));
}
// Start the second driver, and then unbind the root node.
TEST_F(DriverRunnerTest, StartSecondDriver_UnbindRootNode) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
fdf::NodePtr root_node;
driver_host().SetStartHandler(
[this, &node_controller, &root_node](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
auto driver = BindDriver(std::move(request), std::move(node));
root_node = std::move(driver->node());
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr second_node;
EXPECT_EQ(ZX_OK, second_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
BindDriver(std::move(request), std::move(second_node));
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
// Unbinding the root node stops all drivers.
std::vector<size_t> indices;
fidl::WireSharedClient<frunner::ComponentController> root_client(
std::move(*root_driver), dispatcher(), TeardownWatcher(0, indices));
fidl::WireSharedClient<frunner::ComponentController> second_client(
std::move(second_driver), dispatcher(), TeardownWatcher(1, indices));
root_node.Unbind();
EXPECT_TRUE(RunLoopUntilIdle());
EXPECT_THAT(indices, ElementsAre(1, 0));
}
// Start the second driver, and then Stop the root node.
TEST_F(DriverRunnerTest, StartSecondDriver_StopRootNode) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
// These represent the order that Driver::Stop is called
std::vector<size_t> driver_stop_indices;
driver_host().SetStartHandler([this, &node_controller, &driver_stop_indices](
fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
auto driver = BindDriver(std::move(request), std::move(node));
driver->SetStopHandler([&driver_stop_indices, driver]() {
driver_stop_indices.push_back(0);
driver->close_binding();
});
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_host().SetStartHandler(
[this, &driver_stop_indices](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr second_node;
EXPECT_EQ(ZX_OK, second_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
auto driver = BindDriver(std::move(request), std::move(second_node));
driver->SetStopHandler([&driver_stop_indices, driver]() {
driver_stop_indices.push_back(1);
driver->close_binding();
});
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
std::vector<size_t> indices;
fidl::WireSharedClient<frunner::ComponentController> root_client(
std::move(*root_driver), dispatcher(), TeardownWatcher(0, indices));
fidl::WireSharedClient<frunner::ComponentController> second_client(
std::move(second_driver), dispatcher(), TeardownWatcher(1, indices));
// Simulate the Component Framework calling Stop on the root driver.
__UNUSED auto result = root_client->Stop();
EXPECT_TRUE(RunLoopUntilIdle());
// Check that the driver components were shut down in order.
EXPECT_THAT(indices, ElementsAre(1, 0));
// Check that Driver::Stop was called in order.
EXPECT_THAT(driver_stop_indices, ElementsAre(1, 0));
}
// Start the second driver, and then stop the root driver.
TEST_F(DriverRunnerTest, StartSecondDriver_StopRootDriver) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
BindDriver(std::move(request), std::move(node));
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
// Stopping the root driver stops all drivers.
std::vector<size_t> indices;
fidl::WireSharedClient<frunner::ComponentController> root_client(
std::move(*root_driver), dispatcher(), TeardownWatcher(0, indices));
fidl::WireSharedClient<frunner::ComponentController> second_client(
std::move(second_driver), dispatcher(), TeardownWatcher(1, indices));
__UNUSED auto result = root_client->Stop();
EXPECT_TRUE(RunLoopUntilIdle());
EXPECT_THAT(indices, ElementsAre(1, 0));
}
// Start the second driver, stop the root driver, and block while waiting on the
// second driver to shut down.
TEST_F(DriverRunnerTest, StartSecondDriver_BlockOnSecondDriver) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
TestDriver* second_test_driver = nullptr;
driver_host().SetStartHandler(
[this, &second_test_driver](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
second_test_driver = BindDriver(std::move(request), std::move(node));
});
StartDriverHost("driver-hosts", "driver-host-1");
auto second_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/second-driver.cm",
.binary = "driver/second-driver.so",
});
// When the second driver gets asked to stop, don't drop the binding,
// which means DriverRunner will wait for the binding to drop.
second_test_driver->SetStopHandler([]() {});
// Stopping the root driver stops all drivers, but is blocked waiting on the
// second driver to stop.
std::vector<size_t> indices;
fidl::WireSharedClient<frunner::ComponentController> root_client(
std::move(*root_driver), dispatcher(), TeardownWatcher(0, indices));
fidl::WireSharedClient<frunner::ComponentController> second_client(
std::move(second_driver), dispatcher(), TeardownWatcher(1, indices));
__UNUSED auto result = root_client->Stop();
EXPECT_TRUE(RunLoopUntilIdle());
// Nothing has shut down yet, since we are waiting.
EXPECT_THAT(indices, ElementsAre());
// Attempt to add a child node to a removed node.
bool is_error = false;
second_test_driver->node()->AddChild({}, node_controller.NewRequest(dispatcher()), {},
[&is_error](auto result) { is_error = result.is_err(); });
EXPECT_TRUE(RunLoopUntilIdle());
EXPECT_TRUE(is_error);
// Unbind the second node, indicating the second driver has stopped, thereby
// continuing the stop sequence.
second_test_driver->close_binding();
EXPECT_TRUE(RunLoopUntilIdle());
EXPECT_THAT(indices, ElementsAre(1, 0));
}
// Start a composite driver.
TEST_F(DriverRunnerTest, StartCompositeDriver) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("part-1");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.ProtocolA")
.set_target_name("fuchsia.package.RenamedA")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
args.set_name("part-2");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.ProtocolB")
.set_target_name("fuchsia.package.RenamedB")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
auto& entries = start_args.program().entries();
EXPECT_EQ(2u, entries.size());
EXPECT_EQ("binary", entries[0].key);
EXPECT_EQ("driver/composite-driver.so", entries[0].value->str());
EXPECT_EQ("colocate", entries[1].key);
EXPECT_EQ("true", entries[1].value->str());
fdf::NodePtr node;
ASSERT_EQ(ZX_OK, node.Bind(start_args.mutable_node()->TakeChannel()));
BindDriver(std::move(request), std::move(node));
});
auto composite_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/composite-driver.cm",
.binary = "driver/composite-driver.so",
.colocate = true,
});
StopDriverComponent(std::move(root_driver.value()));
}
// Start a driver and inspect the driver runner.
TEST_F(DriverRunnerTest, StartAndInspect) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("second");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.ProtocolA")
.set_target_name("fuchsia.package.RenamedA")));
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.ProtocolB")
.set_target_name("fuchsia.package.RenamedB")));
args.mutable_symbols()->emplace_back(
std::move(fdf::NodeSymbol().set_name("symbol-A").set_address(0x2301)));
args.mutable_symbols()->emplace_back(
std::move(fdf::NodeSymbol().set_name("symbol-B").set_address(0x1985)));
fdf::NodeControllerPtr node_controller;
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
auto heirarchy = Inspect(driver_runner);
ASSERT_EQ("root", heirarchy.node().name());
ASSERT_EQ(3ul, heirarchy.children().size());
// root/node_topology
{
auto node_topology = heirarchy.GetByPath({"node_topology"});
ASSERT_NE(nullptr, node_topology);
ASSERT_EQ(1ul, node_topology->children().size());
ASSERT_EQ(0ul, node_topology->node().properties().size());
}
// root/node_topology/root
{
auto root = heirarchy.GetByPath({"node_topology", "root"});
ASSERT_NE(nullptr, root);
ASSERT_EQ(1ul, root->children().size());
ASSERT_EQ(1ul, root->node().properties().size());
auto driver = root->node().get_property<inspect::StringPropertyValue>("driver");
ASSERT_EQ("fuchsia-boot:///#meta/root-driver.cm", driver->value());
}
// root/node_topology/root/second
{
auto second = heirarchy.GetByPath({"node_topology", "root", "second"});
ASSERT_NE(nullptr, second);
ASSERT_EQ(0ul, second->children().size());
ASSERT_EQ(3ul, second->node().properties().size());
auto offers = second->node().get_property<inspect::StringPropertyValue>("offers");
ASSERT_EQ("fuchsia.package.RenamedA, fuchsia.package.RenamedB", offers->value());
auto symbols = second->node().get_property<inspect::StringPropertyValue>("symbols");
ASSERT_EQ("symbol-A, symbol-B", symbols->value());
auto driver = second->node().get_property<inspect::StringPropertyValue>("driver");
ASSERT_EQ("unbound", driver->value());
}
// root/unbound_composites
{
auto unbound_composites = heirarchy.GetByPath({"unbound_composites"});
ASSERT_EQ(0ul, unbound_composites->children().size());
ASSERT_EQ(0ul, unbound_composites->node().properties().size());
ASSERT_NE(nullptr, unbound_composites);
}
// root/orphan_nodes
{
auto orphan_nodes = heirarchy.GetByPath({"orphan_nodes"});
ASSERT_EQ(0ul, orphan_nodes->children().size());
ASSERT_EQ(0ul, orphan_nodes->node().properties().size());
ASSERT_NE(nullptr, orphan_nodes);
}
StopDriverComponent(std::move(root_driver.value()));
}
// Start a composite driver and inspect the driver runner.
TEST_F(DriverRunnerTest, StartAndInspect_CompositeDriver) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
fdf::NodeControllerPtr node_controller;
driver_host().SetStartHandler(
[this, &node_controller](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr root_node;
EXPECT_EQ(ZX_OK, root_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("part-1");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.ProtocolA")
.set_target_name("fuchsia.package.RenamedA")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
args.set_name("part-2");
args.mutable_offers()->emplace_back().set_protocol(
std::move(fdecl::OfferProtocol()
.set_source_name("fuchsia.package.ProtocolB")
.set_target_name("fuchsia.package.RenamedB")));
root_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(root_node));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_host().SetStartHandler([this](fdf::DriverStartArgs start_args, auto request) {
fdf::NodePtr composite_node;
EXPECT_EQ(ZX_OK, composite_node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
fdf::NodeAddArgs args;
args.set_name("child");
fdf::NodeControllerPtr node_controller;
composite_node->AddChild(std::move(args), node_controller.NewRequest(dispatcher()), {},
[](auto result) { EXPECT_FALSE(result.is_err()); });
BindDriver(std::move(request), std::move(composite_node));
});
auto composite_driver =
StartDriver(driver_runner, {
.url = "fuchsia-boot:///#meta/composite-driver.cm",
.binary = "driver/composite-driver.so",
.colocate = true,
});
auto heirarchy = Inspect(driver_runner);
ASSERT_EQ("root", heirarchy.node().name());
ASSERT_EQ(3ul, heirarchy.children().size());
// root/node_topology
{
auto node_topology = heirarchy.GetByPath({"node_topology"});
ASSERT_NE(nullptr, node_topology);
ASSERT_EQ(1ul, node_topology->children().size());
ASSERT_EQ(0ul, node_topology->node().properties().size());
}
// root/node_topology/root
{
auto root = heirarchy.GetByPath({"node_topology", "root"});
ASSERT_NE(nullptr, root);
ASSERT_EQ(2ul, root->children().size());
ASSERT_EQ(1ul, root->node().properties().size());
auto driver = root->node().get_property<inspect::StringPropertyValue>("driver");
ASSERT_EQ("fuchsia-boot:///#meta/root-driver.cm", driver->value());
}
// root/node_topology/root/part-1
{
auto part1 = heirarchy.GetByPath({"node_topology", "root", "part-1"});
ASSERT_NE(nullptr, part1);
ASSERT_EQ(1ul, part1->children().size());
ASSERT_EQ(2ul, part1->node().properties().size());
auto offers = part1->node().get_property<inspect::StringPropertyValue>("offers");
ASSERT_EQ("fuchsia.package.RenamedA", offers->value());
auto driver = part1->node().get_property<inspect::StringPropertyValue>("driver");
ASSERT_EQ("unbound", driver->value());
}
// root/node_topology/root/composite
{
auto composite = heirarchy.GetByPath({"node_topology", "root", "part-1", "composite"});
ASSERT_NE(nullptr, composite);
ASSERT_EQ(1ul, composite->children().size());
ASSERT_EQ(1ul, composite->node().properties().size());
auto child = composite->GetByPath({"child"});
ASSERT_NE(nullptr, child);
ASSERT_EQ(0ul, child->children().size());
ASSERT_EQ(1ul, child->node().properties().size());
}
// root/node_topology/root/part-2
{
auto part2 = heirarchy.GetByPath({"node_topology", "root", "part-2"});
ASSERT_NE(nullptr, part2);
ASSERT_EQ(1ul, part2->children().size());
ASSERT_EQ(2ul, part2->node().properties().size());
auto offers = part2->node().get_property<inspect::StringPropertyValue>("offers");
ASSERT_EQ("fuchsia.package.RenamedB", offers->value());
}
// root/unbound_composites
{
auto unbound_composites = heirarchy.GetByPath({"unbound_composites"});
ASSERT_NE(nullptr, unbound_composites);
}
// root/orphan_nodes
{
auto orphan_nodes = heirarchy.GetByPath({"orphan_nodes"});
ASSERT_NE(nullptr, orphan_nodes);
}
StopDriverComponent(std::move(root_driver.value()));
}
TEST_F(DriverRunnerTest, TestTearDownNodeTreeWithManyChildren) {
auto driver_index = CreateDriverIndex();
auto driver_index_client = driver_index.Connect();
ASSERT_EQ(ZX_OK, driver_index_client.status_value());
DriverRunner driver_runner(ConnectToRealm(), std::move(*driver_index_client), inspector(),
dispatcher());
auto defer = fit::defer([this] { Unbind(); });
std::vector<fdf::NodeControllerPtr> node_controller;
std::vector<fdf::NodePtr> nodes;
driver_host().SetStartHandler(
[this, &node_controller, &nodes](fdf::DriverStartArgs start_args, auto request) {
realm().SetCreateChildHandler(
[](fdecl::CollectionRef collection, fdecl::Child decl, auto offers) {});
realm().SetOpenExposedDirHandler([this](fdecl::ChildRef child, auto exposed_dir) {
driver_dir().Bind(std::move(exposed_dir));
});
fdf::NodePtr node;
EXPECT_EQ(ZX_OK, node.Bind(std::move(*start_args.mutable_node()), dispatcher()));
for (size_t i = 0; i < 100; i++) {
fdf::NodeAddArgs args;
args.set_name(std::string("child") + std::to_string(i));
fdf::NodeControllerPtr ptr;
node->AddChild(std::move(args), ptr.NewRequest(dispatcher()), {},
[&node_controller, ptr = std::move(ptr)](auto result) mutable {
EXPECT_FALSE(result.is_err());
node_controller.emplace_back(std::move(ptr));
});
}
auto driver = BindDriver(std::move(request), std::move(node));
nodes.emplace_back(std::move(driver->node()));
});
auto root_driver = StartRootDriver("fuchsia-boot:///#meta/root-driver.cm", driver_runner);
ASSERT_EQ(ZX_OK, root_driver.status_value());
Unbind();
}
TEST_F(DriverRunnerTest, TestBindResultTracker) {
bool callback_called = false;
bool* callback_called_ptr = &callback_called;
auto callback = [callback_called_ptr](
fidl::VectorView<fuchsia_driver_development::wire::NodeBindingInfo> results) {
ASSERT_EQ(std::string_view("node_name"), results[0].node_name().get());
ASSERT_EQ(std::string_view("driver_url"), results[0].driver_url().get());
ASSERT_EQ(1ul, results.count());
*callback_called_ptr = true;
};
BindResultTracker tracker(3, std::move(callback));
ASSERT_EQ(false, callback_called);
tracker.ReportNoBind();
ASSERT_EQ(false, callback_called);
tracker.ReportSuccessfulBind(std::string_view("node_name"), std::string_view("driver_url"));
ASSERT_EQ(false, callback_called);
tracker.ReportNoBind();
ASSERT_EQ(true, callback_called);
callback_called = false;
auto callback_two =
[callback_called_ptr](
fidl::VectorView<fuchsia_driver_development::wire::NodeBindingInfo> results) {
ASSERT_EQ(0ul, results.count());
*callback_called_ptr = true;
};
BindResultTracker tracker_two(3, std::move(callback_two));
ASSERT_EQ(false, callback_called);
tracker_two.ReportNoBind();
ASSERT_EQ(false, callback_called);
tracker_two.ReportNoBind();
ASSERT_EQ(false, callback_called);
tracker_two.ReportNoBind();
ASSERT_EQ(true, callback_called);
callback_called = false;
auto callback_three =
[callback_called_ptr](
fidl::VectorView<fuchsia_driver_development::wire::NodeBindingInfo> results) {
ASSERT_EQ(std::string_view("node_name"), results[0].node_name().get());
ASSERT_EQ(std::string_view("driver_url"), results[0].driver_url().get());
ASSERT_EQ(1ul, results.count());
*callback_called_ptr = true;
};
BindResultTracker tracker_three(3, std::move(callback_three));
ASSERT_EQ(false, callback_called);
tracker_three.ReportNoBind();
ASSERT_EQ(false, callback_called);
tracker_three.ReportNoBind();
ASSERT_EQ(false, callback_called);
tracker_three.ReportSuccessfulBind(std::string_view("node_name"), std::string_view("driver_url"));
ASSERT_EQ(true, callback_called);
}
TEST(CompositeDirOfferTest, WorkingOffer) {
const std::string_view kServiceName = "fuchsia.service";
fidl::Arena<> arena;
auto service = fcd::wire::OfferDirectory::Builder(arena);
service.source_name(arena, kServiceName);
service.target_name(arena, std::string(kServiceName) + "-default");
service.rights(fuchsia_io::wire::kRwStarDir);
service.dependency_type(fcd::wire::DependencyType::kStrong);
auto offer = fcd::wire::Offer::WithDirectory(arena, service.Build());
auto new_offer = CreateCompositeDirOffer(arena, offer, "parent_node");
ASSERT_TRUE(new_offer);
std::string new_target(new_offer->directory().target_name().data(),
new_offer->directory().target_name().size());
ASSERT_EQ(new_target, std::string(kServiceName).append("-parent_node"));
}
TEST(CompositeServiceOfferTest, WorkingOffer) {
const std::string_view kServiceName = "fuchsia.service";
fidl::Arena<> arena;
auto service = fcd::wire::OfferService::Builder(arena);
service.source_name(arena, kServiceName);
service.target_name(arena, kServiceName);
fidl::VectorView<fcd::wire::NameMapping> mappings(arena, 2);
mappings[0].source_name = fidl::StringView(arena, "instance-1");
mappings[0].target_name = fidl::StringView(arena, "default");
mappings[1].source_name = fidl::StringView(arena, "instance-1");
mappings[1].target_name = fidl::StringView(arena, "instance-2");
service.renamed_instances(mappings);
fidl::VectorView<fidl::StringView> filters(arena, 2);
filters[0] = fidl::StringView(arena, "default");
filters[1] = fidl::StringView(arena, "instance-2");
service.source_instance_filter(filters);
auto offer = fcd::wire::Offer::WithService(arena, service.Build());
auto new_offer = CreateCompositeServiceOffer(arena, offer, "parent_node", false);
ASSERT_TRUE(new_offer);
ASSERT_EQ(2ul, new_offer->service().renamed_instances().count());
// Check that the default instance got renamed.
ASSERT_EQ(std::string("instance-1"),
std::string(new_offer->service().renamed_instances()[0].source_name.get()));
ASSERT_EQ(std::string("parent_node"),
std::string(new_offer->service().renamed_instances()[0].target_name.get()));
// Check that a non-default instance stayed the same.
ASSERT_EQ(std::string("instance-1"),
std::string(new_offer->service().renamed_instances()[1].source_name.get()));
ASSERT_EQ(std::string("instance-2"),
std::string(new_offer->service().renamed_instances()[1].target_name.get()));
ASSERT_EQ(2ul, new_offer->service().source_instance_filter().count());
// Check that the default filter got renamed.
ASSERT_EQ(std::string("parent_node"),
std::string(new_offer->service().source_instance_filter()[0].get()));
// Check that a non-default filter stayed the same.
ASSERT_EQ(std::string("instance-2"),
std::string(new_offer->service().source_instance_filter()[1].get()));
}
TEST(CompositeServiceOfferTest, WorkingOfferPrimary) {
const std::string_view kServiceName = "fuchsia.service";
fidl::Arena<> arena;
auto service = fcd::wire::OfferService::Builder(arena);
service.source_name(arena, kServiceName);
service.target_name(arena, kServiceName);
fidl::VectorView<fcd::wire::NameMapping> mappings(arena, 2);
mappings[0].source_name = fidl::StringView(arena, "instance-1");
mappings[0].target_name = fidl::StringView(arena, "default");
mappings[1].source_name = fidl::StringView(arena, "instance-1");
mappings[1].target_name = fidl::StringView(arena, "instance-2");
service.renamed_instances(mappings);
fidl::VectorView<fidl::StringView> filters(arena, 2);
filters[0] = fidl::StringView(arena, "default");
filters[1] = fidl::StringView(arena, "instance-2");
service.source_instance_filter(filters);
auto offer = fcd::wire::Offer::WithService(arena, service.Build());
auto new_offer = CreateCompositeServiceOffer(arena, offer, "parent_node", true);
ASSERT_TRUE(new_offer);
ASSERT_EQ(3ul, new_offer->service().renamed_instances().count());
// Check that the default instance stayed the same (because we're primary).
ASSERT_EQ(std::string("instance-1"),
std::string(new_offer->service().renamed_instances()[0].source_name.get()));
ASSERT_EQ(std::string("default"),
std::string(new_offer->service().renamed_instances()[0].target_name.get()));
// Check that the default instance got renamed.
ASSERT_EQ(std::string("instance-1"),
std::string(new_offer->service().renamed_instances()[1].source_name.get()));
ASSERT_EQ(std::string("parent_node"),
std::string(new_offer->service().renamed_instances()[1].target_name.get()));
// Check that a non-default instance stayed the same.
ASSERT_EQ(std::string("instance-1"),
std::string(new_offer->service().renamed_instances()[2].source_name.get()));
ASSERT_EQ(std::string("instance-2"),
std::string(new_offer->service().renamed_instances()[2].target_name.get()));
ASSERT_EQ(3ul, new_offer->service().source_instance_filter().count());
// Check that the default filter stayed the same (because we're primary).
EXPECT_EQ(std::string("default"),
std::string(new_offer->service().source_instance_filter()[0].get()));
// Check that the default filter got renamed.
EXPECT_EQ(std::string("parent_node"),
std::string(new_offer->service().source_instance_filter()[1].get()));
// Check that a non-default filter stayed the same.
EXPECT_EQ(std::string("instance-2"),
std::string(new_offer->service().source_instance_filter()[2].get()));
}