blob: 23949837f9473b50827ff665517412bd794595c7 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/devices/bin/driver_manager/driver_host_runner.h"
#include <fidl/fuchsia.component.decl/cpp/test_base.h>
#include <fidl/fuchsia.component/cpp/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/fdio/directory.h>
#include <lib/fidl/cpp/binding.h>
#include <zircon/errors.h>
#include <gtest/gtest.h>
#include "src/devices/bin/driver_loader/loader.h"
#include "src/devices/bin/driver_manager/tests/driver_runner_test_fixture.h"
#include "src/devices/bin/driver_manager/tests/test_pkg.h"
namespace {
namespace fcomponent = fuchsia_component;
namespace fdata = fuchsia_data;
namespace fdfw = fuchsia_driver_framework;
namespace fdecl = fuchsia_component_decl;
namespace fio = fuchsia::io;
namespace frunner = fuchsia_component_runner;
// Returns the exit status of the process.
// TODO(https://fxbug.dev/349913885): this will eventually be included in the bootstrap halper
// library.
int64_t WaitForProcessExit(const zx::process& process);
class DriverHostRunnerTest : public gtest::TestLoopFixture {
public:
DriverHostRunnerTest() : realm_(dispatcher()) {}
void SetUp() {
dynamic_linker_ = driver_loader::Loader::Create(dispatcher());
driver_host_runner_ =
std::make_unique<driver_manager::DriverHostRunner>(dispatcher(), ConnectToRealm());
}
protected:
// Creates the driver host component, loads the driver host and waits for it to exit.
// |driver_host_path| is the local package path to the binary to pass to the driver host runner.
//
// |expected_libs| holds that names of the libraries that are needed by the driver host.
// This list will be used to construct the test files that the driver host runner expects
// to be present in the "/pkg/libs" dir that will be passed to the dynamic linker.
// No additional validation is done on the strings in |expected_libs|.
void StartDriverHost(std::string_view driver_host_path,
const std::vector<std::string_view> expected_libs);
fidl::ClientEnd<fuchsia_component::Realm> ConnectToRealm();
fidl::ClientEnd<fuchsia_driver_loader::DriverHostLauncher> ConnectToDynamicLinker();
driver_runner::TestRealm& realm() { return realm_; }
private:
driver_runner::TestRealm realm_;
std::optional<fidl::ServerBinding<fuchsia_component::Realm>> realm_binding_;
std::unique_ptr<driver_loader::Loader> dynamic_linker_;
std::unique_ptr<driver_manager::DriverHostRunner> driver_host_runner_;
driver_runner::TestDirectory driver_host_dir_{dispatcher()};
};
void DriverHostRunnerTest::StartDriverHost(std::string_view driver_host_path,
const std::vector<std::string_view> expected_libs) {
constexpr std::string_view kDriverHostName = "driver-host-new-";
constexpr std::string_view kCollection = "driver-hosts";
constexpr std::string_view kComponentUrl = "fuchsia-boot:///driver_host2#meta/driver_host2.cm";
bool created_component;
realm().SetCreateChildHandler(
[&](fdecl::CollectionRef collection, fdecl::Child decl, std::vector<fdecl::Offer> offers) {
EXPECT_EQ(kDriverHostName, decl.name().value().substr(0, kDriverHostName.size()));
EXPECT_EQ(kCollection, collection.name());
EXPECT_EQ(kComponentUrl, decl.url());
created_component = true;
});
auto endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
realm().SetOpenExposedDirHandler(
[this, kCollection, kDriverHostName](fdecl::ChildRef child, auto exposed_dir) {
EXPECT_EQ(kCollection, child.collection().value_or(""));
EXPECT_EQ(kDriverHostName, child.name().substr(0, kDriverHostName.size()));
driver_host_dir_.Bind(std::move(exposed_dir));
});
fidl::WireSharedClient<fuchsia_driver_loader::DriverHostLauncher> launcher(
ConnectToDynamicLinker(), dispatcher());
std::shared_ptr<bool> connected = std::make_shared<bool>(false);
bool got_cb = false;
driver_host_runner_->StartDriverHost(
std::move(launcher), std::move(endpoints.server), connected,
[&](zx::result<fidl::ClientEnd<fuchsia_driver_loader::DriverHost>> result) {
ASSERT_EQ(ZX_OK, result.status_value());
ASSERT_TRUE(result->is_valid());
got_cb = true;
});
ASSERT_TRUE(RunLoopUntilIdle());
ASSERT_TRUE(created_component);
auto pkg_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create();
test_utils::TestPkg test_pkg(std::move(pkg_endpoints.server), driver_host_path,
"bin/driver_host2", expected_libs);
ASSERT_NO_FATAL_FAILURE(driver_runner::DriverHostComponentStart(realm(), *driver_host_runner_,
std::move(pkg_endpoints.client)));
ASSERT_TRUE(RunLoopUntilIdle());
ASSERT_TRUE(got_cb);
ASSERT_TRUE(*connected);
std::unordered_set<const driver_manager::DriverHostRunner::DriverHost*> driver_hosts =
driver_host_runner_->DriverHosts();
ASSERT_EQ(1u, driver_hosts.size());
const zx::process& process = (*driver_hosts.begin())->process();
ASSERT_EQ(0, WaitForProcessExit(process));
}
fidl::ClientEnd<fuchsia_component::Realm> DriverHostRunnerTest::ConnectToRealm() {
auto realm_endpoints = fidl::Endpoints<fcomponent::Realm>::Create();
realm_binding_.emplace(dispatcher(), std::move(realm_endpoints.server), &realm_,
fidl::kIgnoreBindingClosure);
return std::move(realm_endpoints.client);
}
fidl::ClientEnd<fuchsia_driver_loader::DriverHostLauncher>
DriverHostRunnerTest::ConnectToDynamicLinker() {
auto [client_end, server_end] =
fidl::Endpoints<fuchsia_driver_loader::DriverHostLauncher>::Create();
dynamic_linker_->Connect(std::move(server_end));
return std::move(client_end);
}
int64_t WaitForProcessExit(const zx::process& process) {
int64_t result = -1;
auto wait_for_termination = [&process, &result]() {
zx_signals_t signals;
ASSERT_EQ(process.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), &signals), ZX_OK);
ASSERT_TRUE(signals & ZX_PROCESS_TERMINATED);
zx_info_process_t info;
ASSERT_EQ(process.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr), ZX_OK);
ASSERT_TRUE(info.flags & ZX_INFO_PROCESS_FLAG_STARTED);
ASSERT_TRUE(info.flags & ZX_INFO_PROCESS_FLAG_EXITED);
result = info.return_code;
};
wait_for_termination();
return result;
}
TEST_F(DriverHostRunnerTest, StartDriverHost) {
constexpr std::string_view kDriverHostPath = "/pkg/bin/driver_host2";
const std::vector<std::string_view> kExpectedLibs;
StartDriverHost(kDriverHostPath, kExpectedLibs);
}
TEST_F(DriverHostRunnerTest, StartFakeDriverHost) {
constexpr std::string_view kDriverHostPath = "/pkg/bin/fake_driver_host";
const std::vector<std::string_view> kExpectedLibs = {
"libdh-deps-a.so",
"libdh-deps-b.so",
"libdh-deps-c.so",
};
StartDriverHost(kDriverHostPath, kExpectedLibs);
}
class DynamicLinkingTest : public driver_runner::DriverRunnerTestBase {};
TEST_F(DynamicLinkingTest, StartRootDriver) {
auto driver_host_runner =
std::make_unique<driver_manager::DriverHostRunner>(dispatcher(), ConnectToRealm());
SetupDriverRunnerWithDynamicLinker(dispatcher(), std::move(driver_host_runner),
1u /* wait_for_num_drivers */);
auto root_driver = StartRootDriverDynamicLinking();
ASSERT_EQ(ZX_OK, root_driver.status_value());
std::unordered_set<const driver_manager::DriverHostRunner::DriverHost*> driver_hosts =
driver_runner().driver_host_runner_for_tests()->DriverHosts();
ASSERT_EQ(1u, driver_hosts.size());
const zx::process& process = (*driver_hosts.begin())->process();
ASSERT_EQ(24, WaitForProcessExit(process));
StopDriverComponent(std::move(root_driver->controller));
realm().AssertDestroyedChildren({driver_runner::CreateChildRef("root", "boot-drivers")});
}
TEST_F(DynamicLinkingTest, StartCompatDriver) {
auto driver_host_runner =
std::make_unique<driver_manager::DriverHostRunner>(dispatcher(), ConnectToRealm());
SetupDriverRunnerWithDynamicLinker(dispatcher(), std::move(driver_host_runner),
2u /* wait_for_num_drivers */);
auto root_driver = StartRootDriverDynamicLinking();
ASSERT_EQ(ZX_OK, root_driver.status_value());
driver_index().set_match_callback([](auto args) -> zx::result<FakeDriverIndex::MatchResult> {
return zx::ok(FakeDriverIndex::MatchResult{
.url = driver_runner::compat_driver_url,
});
});
PrepareRealmForDriverComponentStart("dev.compat", driver_runner::compat_driver_url);
fdfw::NodeAddArgs args({
.name = "compat",
});
std::shared_ptr<driver_runner::CreatedChild> child =
root_driver->driver->AddChild(std::move(args), false, false);
EXPECT_TRUE(RunLoopUntilIdle());
bool did_bind = false;
child->node_controller.value()->WaitForDriver().Then(
[&did_bind](fidl::Result<fuchsia_driver_framework::NodeController::WaitForDriver>& result) {
if (result.is_ok() && result.value().driver_started_node_token().has_value()) {
did_bind = true;
return;
}
ZX_ASSERT_MSG(false, " WaitForDriver failed: %s.",
result.error_value().FormatDescription().c_str());
});
auto compat_driver_config = driver_runner::kCompatDriverPkgConfig;
std::string binary = std::string(compat_driver_config.main_module.open_path);
std::string v1_binary = std::string(compat_driver_config.additional_modules[0].open_path);
StartDriverHandler start_handler = [&](driver_runner::TestDriver* driver,
fdfw::DriverStartArgs start_args) {
EXPECT_FALSE(start_args.symbols().has_value());
ValidateProgram(start_args.program(), binary, "true" /* colocate */, "false", "false",
"true" /* use_dynamic_linker */, v1_binary);
};
auto [driver, controller] =
StartDriverWithConfig("dev.compat",
{
.url = "fuchsia-boot:///#meta/compat-driver.cm",
.binary = binary,
.colocate = true,
.use_dynamic_linker = true,
.compat = v1_binary,
},
std::move(start_handler), compat_driver_config);
EXPECT_TRUE(did_bind);
ServeStopListener(std::move(controller));
// Check the driver host process exited with the expected value.
std::unordered_set<const driver_manager::DriverHostRunner::DriverHost*> driver_hosts =
driver_runner().driver_host_runner_for_tests()->DriverHosts();
ASSERT_EQ(1u, driver_hosts.size());
const zx::process& process = (*driver_hosts.begin())->process();
// The root driver will return 24, and the compat driver will return 5.
ASSERT_EQ(29, WaitForProcessExit(process));
driver->CloseBinding();
driver->DropNode();
StopDriverComponent(std::move(root_driver->controller));
realm().AssertDestroyedChildren({driver_runner::CreateChildRef("root", "boot-drivers"),
driver_runner::CreateChildRef("dev.compat", "boot-drivers")});
}
// Starts the root driver with dynamic linking and attempts to start the colocated second driver
// without.
TEST_F(DynamicLinkingTest, StartColocatedSecondDriverNoDynamicLinking) {
auto driver_host_runner =
std::make_unique<driver_manager::DriverHostRunner>(dispatcher(), ConnectToRealm());
SetupDriverRunnerWithDynamicLinker(dispatcher(), std::move(driver_host_runner));
auto root_driver = StartRootDriverDynamicLinking();
ASSERT_EQ(ZX_OK, root_driver.status_value());
PrepareRealmForSecondDriverComponentStart();
fdfw::NodeAddArgs args({
.name = "second",
});
std::shared_ptr<driver_runner::CreatedChild> child =
root_driver->driver->AddChild(std::move(args), false, false);
EXPECT_TRUE(RunLoopUntilIdle());
bool bind_failed = false;
child->node_controller.value()->WaitForDriver().Then(
[&bind_failed](
fidl::Result<fuchsia_driver_framework::NodeController::WaitForDriver>& result) {
if (result.is_ok() && result.value().start_error().has_value()) {
bind_failed = true;
return;
}
ZX_ASSERT_MSG(false, " WaitForDriver did not get expected error");
});
auto [driver, controller] = StartSecondDriver("dev.second", true /* colocate */, false, false,
false /* use_dynamic_linker */);
// Starting the driver should fail, as the driver host is configured to use dynamic linking.
EXPECT_EQ(nullptr, driver);
// Mark the boot-up as complete so the error gets emitted out.
driver_runner().BootupDoneForTesting();
RunLoopUntilIdle();
EXPECT_TRUE(bind_failed);
ServeStopListener(std::move(controller));
StopDriverComponent(std::move(root_driver->controller));
realm().AssertDestroyedChildren({driver_runner::CreateChildRef("root", "boot-drivers"),
driver_runner::CreateChildRef("dev.second", "boot-drivers")});
}
} // namespace