驱动程序集成测试框架
DriverTestRealm 是一种集成测试框架,可在封闭环境中运行驱动程序。它有助于驱动程序作者测试其驱动程序,也有助于系统开发者运行使用特定驱动程序堆栈的集成测试。 DriverTestRealm 提供了所有驱动程序框架 API 的封闭版本,并提供了一个与运行系统几乎相同的环境。
DriverTestRealm 用于集成测试。如需轻量级单元测试 框架,请改用 单元测试库 。
DriverTestRealm 概览
DriverTestRealm 是测试可以访问的组件。此组件包含 DriverFramework 的所有组件,例如 DriverManager 和 DriverIndex。它会模拟这些组件所需的所有功能。

DriverTestRealm 公开了用于启动 DriverTestRealm 的
fuchsia.driver.test/Realm 协议
。Start 函数接受一些实参,这些实参可用于配置加载的驱动程序、根驱动程序以及 DriverManager 关闭方式等内容。每个组件只能调用一次 Start;如果每个测试都需要新的 DriverTestRealm,则必须使用 RealmBuilder。
与驱动程序互动
包含驱动程序
默认情况下,DriverTestRealm 组件会从自己的软件包加载驱动程序。测试作者必须确保在测试软件包中包含他们希望加载的所有驱动程序。
绑定驱动程序
默认情况下,DriverTest 领域的根驱动程序是 test-parent 驱动程序。这意味着,如需绑定驱动程序,您应创建一个绑定到测试父级的模拟驱动程序。这可以通过以下绑定规则来实现:
fuchsia.BIND_PROTOCOL == fuchsia.test.BIND_PROTOCOL.PARENT;
然后,您的模拟驱动程序可以添加具有正确属性的设备,以便您的受测驱动程序绑定到模拟驱动程序。
封闭与非封闭
使用 DriverTestRealm 的方式有两种。它可以封闭或非封闭的方式使用。
Hermetic
在 Driver Test Realm 的封闭版本中,每个测试都有自己的 Driver Test Realm 组件版本。这意味着每个测试都与其他测试封闭或隔离。测试不会共享任何状态,因为每个 Driver Test Realm 都是该测试独有的。

使用封闭的 Driver Test Realm 可能会比较慢,因为每个测试都必须生成和设置新组件。
非封闭
使用 Driver Test Realm 的非封闭方式是让每个测试实例共享一个 Driver Test Realm 子组件。

测试作者需要格外小心,确保在每次测试之间清除驱动程序的状态,以便各个测试不会相互影响。
使用非封闭的 Driver Test Realm 可能会快得多,因为每个测试都不需要生成和设置新组件。测试代码也可能更简单。
DriverTestRealm 示例
以下是一些在 C++ 和 Rust 中以封闭和非封闭方式使用 DriverTestRealm 的示例。
这些示例位于 //examples/drivers/driver_test_realm/。
Hermetic
测试作者可以使用 RealmBuilder 为每个测试创建新的 DriverTestRealm。 DriverFramework 提供了一个有用的库,用于在 RealmBuilder 中使用 DriverTestRealm。
以下是使用 RealmBuilder 的 DriverTestRealm 的 BUILD.gn 文件示例。
请注意,有一个特定的 DriverTestRealm GN 目标需要依赖,以便测试生成的 CML 具有正确的 RealmBuilder 权限。
C++
test("driver_test_realm_example_hermetic_cpp") {
sources = [ "test.cc" ]
deps = [
"//examples/drivers/driver_test_realm/sample_driver:fuchsia.hardware.sample_cpp",
"//sdk/fidl/fuchsia.driver.test:fuchsia.driver.test_cpp",
"//sdk/lib/async-loop",
"//sdk/lib/async-loop:async-loop-cpp",
"//sdk/lib/component/outgoing/cpp",
"//sdk/lib/device-watcher/cpp",
"//sdk/lib/driver_test_realm/realm_builder/cpp",
"//src/lib/fxl/test:gtest_main",
"//src/lib/testing/loop_fixture",
"//zircon/system/ulib/fbl",
]
}
fuchsia_unittest_package("package") {
package_name = "driver_test_realm_example_hermetic_cpp"
deps = [
# Include your test component.
":driver_test_realm_example_hermetic_cpp",
# Include the driver(s) you will be testing.
"//examples/drivers/driver_test_realm/sample_driver",
# Include the test parent (if your driver binds to it).
"//src/devices/misc/drivers/test-parent",
]
}
Rust
rustc_test("driver_test_realm_example_realm_builder_rust") {
edition = "2024"
testonly = true
source_root = "test.rs"
sources = [ "test.rs" ]
deps = [
"//examples/drivers/driver_test_realm/sample_driver:fuchsia.hardware.sample_rust",
"//sdk/fidl/fuchsia.driver.test:fuchsia.driver.test_rust",
"//sdk/lib/device-watcher/rust",
"//sdk/lib/driver_test_realm/realm_builder/rust",
"//src/lib/fuchsia",
"//src/lib/fuchsia-component-test",
"//third_party/rust_crates:anyhow",
]
}
fuchsia_unittest_package("package") {
package_name = "driver_test_realm_example_realm_builder_rust"
deps = [
# Include your test component.
":driver_test_realm_example_realm_builder_rust",
# Include the driver(s) you will be testing.
"//examples/drivers/driver_test_realm/sample_driver",
# Include the platform bus (if your driver binds to it).
"//src/devices/bus/drivers/platform:platform-bus",
# Include the test parent (if your driver binds to it).
"//src/devices/misc/drivers/test-parent",
]
# There's expected error logs that happen due to races in driver enumeration.
test_specs = {
log_settings = {
max_severity = "ERROR"
}
}
}
以下是为每个测试生成新的 DriverTestRealm 的测试代码。
C++
class DriverTestRealmTest : public gtest::TestLoopFixture {};
TEST_F(DriverTestRealmTest, DriversExist) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
loop.StartThread("bg");
// Create and build the realm.
auto realm_builder = component_testing::RealmBuilder::Create();
driver_test_realm::Setup(realm_builder, loop.dispatcher(), driver_test_realm::Options{},
fuchsia_driver_test::RealmArgs{});
auto realm = realm_builder.Build(loop.dispatcher());
auto boot_result = driver_test_realm::WaitForBootup(realm);
ASSERT_EQ(ZX_OK, boot_result.status_value());
fbl::unique_fd fd;
auto exposed = realm.component().CloneExposedDir();
ASSERT_EQ(ZX_OK, fdio_fd_create(exposed.TakeChannel().release(), fd.reset_and_get_address()));
// Wait for driver.
auto node = driver_test_realm::WaitForNode(realm, "dev.sys.test.sample_driver");
ASSERT_TRUE(node.is_ok());
// TODO(https://fxbug.dev/377735979): Connect using a different mechanism.
zx::result channel =
device_watcher::RecursiveWaitForFile(fd.get(), "dev-topological/sys/test/sample_driver");
ASSERT_EQ(channel.status_value(), ZX_OK);
fidl::ClientEnd<fuchsia_hardware_sample::Echo> client(std::move(*channel));
// Send a FIDL request.
constexpr std::string_view sent_string = "hello";
fidl::WireResult result =
fidl::WireCall(client)->EchoString(fidl::StringView::FromExternal(sent_string));
ASSERT_EQ(ZX_OK, result.status());
ASSERT_EQ(sent_string, result.value().response.get());
driver_test_realm::ShutdownRealm(realm);
}
Rust
#[fuchsia::test]
async fn test_sample_driver() -> Result<()> {
// Create the RealmBuilder.
let builder = RealmBuilder::new().await?;
builder.driver_test_realm_setup().await?;
// Build the Realm.
let instance = builder.build().await?;
// Start DriverTestRealm
instance.driver_test_realm_start(fdt::RealmArgs::default()).await?;
// Connect to our driver.
let dev = instance.driver_test_realm_connect_to_dev()?;
let driver =
device_watcher::recursive_wait_and_open::<fidl_fuchsia_hardware_sample::EchoMarker>(
&dev,
"sys/test/sample_driver",
)
.await?;
// Call a FIDL method on the driver.
let response = driver.echo_string("Hello world!").await.unwrap();
// Verify the response.
assert_eq!(response, "Hello world!");
Ok(())
}
#[fuchsia::test]
async fn test_platform_bus() -> Result<()> {
// Create the RealmBuilder.
let builder = RealmBuilder::new().await?;
builder.driver_test_realm_setup().await?;
// Build the Realm.
let instance = builder.build().await?;
// Start DriverTestRealm.
let args = fdt::RealmArgs {
root_driver: Some("fuchsia-boot:///platform-bus#meta/platform-bus.cm".to_string()),
..Default::default()
};
instance.driver_test_realm_start(args).await?;
// Connect to our driver.
let dev = instance.driver_test_realm_connect_to_dev()?;
device_watcher::recursive_wait(&dev, "sys/platform").await?;
Ok(())
}
非封闭
以下是一个基本测试示例,该测试会启动 DriverTestRealm 组件,然后连接到 /dev 以查看已加载的驱动程序。
首先,务必正确设置 build 规则。测试软件包需要包含 DriverTestRealm 组件,以及将要加载的所有驱动程序。将驱动程序添加到软件包会自动使这些驱动程序对 DriverTestRealm 可见。
C++
test("driver_test_realm_example_non_hermetic_cpp") {
sources = [ "test.cc" ]
deps = [
"//sdk/fidl/fuchsia.driver.test:fuchsia.driver.test_cpp",
"//sdk/lib/component/incoming/cpp",
"//sdk/lib/device-watcher/cpp",
"//sdk/lib/driver_test_realm:static",
"//sdk/lib/syslog/cpp",
"//third_party/googletest:gtest",
]
}
fuchsia_unittest_package("package") {
package_name = "driver_test_realm_example_non_hermetic_cpp"
deps = [
":driver_test_realm_example_non_hermetic_cpp",
# Add drivers to the package here.
# The test-parent driver is the default root driver for DriverTestRealm.
"//src/devices/misc/drivers/test-parent",
]
}
测试设置大致如下所示。请注意,您必须先调用 fuchsia.driver.test/Realm:Start,然后才能运行测试框架。您可以设置 Start 的实参来配置 DriverManager 实现。
C++
TEST(DdkFirmwaretest, DriverWasLoaded) {
zx::result channel = device_watcher::RecursiveWaitForFile("/dev/sys/test");
ASSERT_EQ(channel.status_value(), ZX_OK);
}
int main(int argc, char **argv) {
fuchsia_logging::LogSettingsBuilder builder;
builder.WithTags({"driver_test_realm_test"}).BuildAndInitialize();
// Connect to DriverTestRealm.
auto client_end = component::Connect<fuchsia_driver_test::Realm>();
if (!client_end.is_ok()) {
FX_LOG_KV(ERROR, "Failed to connect to Realm FIDL", FX_KV("error", client_end.error_value()));
return 1;
}
fidl::WireSyncClient client{std::move(*client_end)};
// Start the DriverTestRealm with correct arguments.
auto wire_result = client->Start(fuchsia_driver_test::wire::RealmArgs());
if (wire_result.status() != ZX_OK) {
FX_LOG_KV(ERROR, "Failed to call to Realm:Start", FX_KV("status", wire_result.status()));
return 1;
}
if (wire_result.value().is_error()) {
FX_LOG_KV(ERROR, "Realm:Start failed", FX_KV("status", wire_result.value().error_value()));
return 1;
}
// Run the tests.
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
请注意,DriverTestRealm 启动后,测试环境会在其组件命名空间中提供 DriverFramework 的所有 API。
您可以观看并打开 /dev/ 目录以连接到驱动程序。
简单示例
Simple 库允许测试使用带有默认实参的 DriverTestRealm。
大多数集成测试都可以使用 DriverTestRealm 的默认配置;它们不需要将实参传递给 Start。如果是这种情况,SimpleDriverTestRealm 会自动启动。
C++
test("driver_test_realm_example_simple_cpp") {
sources = [ "test.cc" ]
deps = [
"//sdk/lib/device-watcher/cpp",
"//sdk/lib/driver_test_realm/simple",
"//src/lib/fxl/test:gtest_main",
]
}
fuchsia_unittest_package("package") {
package_name = "driver_test_realm_example_simple_cpp"
deps = [ ":driver_test_realm_example_simple_cpp" ]
}
Rust
rustc_test("driver_test_realm_example_simple_rust") {
edition = "2024"
source_root = "test.rs"
sources = [ "test.rs" ]
deps = [
"//sdk/lib/device-watcher/rust",
"//sdk/lib/driver_test_realm/simple",
"//src/lib/fuchsia",
"//src/lib/fuchsia-fs",
"//third_party/rust_crates:anyhow",
]
}
fuchsia_unittest_package("package") {
package_name = "driver_test_realm_example_simple_rust"
deps = [ ":driver_test_realm_example_simple_rust" ]
}
该测试看起来完全相同,只是不需要设置 main 函数来调用 fuchsia.driver.test/Realm:Start。
C++
TEST(SimpleDriverTestRealmTest, DriversExist) {
zx::result channel = device_watcher::RecursiveWaitForFile("/dev/sys/test");
ASSERT_EQ(channel.status_value(), ZX_OK);
}
Rust
#[fuchsia::test]
async fn test_driver() -> Result<()> {
let dev = fuchsia_fs::directory::open_in_namespace("/dev", fuchsia_fs::Flags::empty())?;
device_watcher::recursive_wait(&dev, "sys/test").await?;
Ok(())
}
常见问题:
- 驱动程序未绑定
- 确保驱动程序包含在软件包中
- 如果使用默认根驱动程序,请确保包含
src/devices/misc/drivers/test-parent。
- 对 /dev/ 的调用挂起
- 确保调用
fuchsia.driver.test/Realm:Start。
- 确保调用
fuchsia.driver.test/Realm:Start返回 ZX_ERR_ALREADY_BOUND- 每个组件只能调用一次 Start。如果您希望每个测试都有一个新的 DriverTestRealm,请参阅 RealmBuilder 部分。