驱动程序集成测试框架
DriverTestRealm 是一种集成测试框架,可在封闭环境中运行驱动程序。驱动程序开发者可用于测试其驱动程序,也便于系统开发者运行使用特定驱动程序堆栈的集成测试。DriverTestRealm 为所有 Driver Framework API 提供封闭版本,并提供与正在运行的系统几乎完全相同的环境。
DriverTestRealm 用于进行集成测试。对于轻量级单元测试框架,请改用模拟 DDK。
DriverTestRealm 概览
DriverTestRealm 是测试可以访问的组件。此组件包含 DriverFramework 的所有组件,如 DriverManager 和 DriverIndex。它会模拟这些组件所需的所有功能。
DriverTestRealm 公开用于启动 DriverTestRealm 的 fuchsia.driver.test/Realm
协议。Start 函数采用参数,可用于配置加载哪些驱动程序、根驱动程序以及 DriverManager 如何关闭等内容。每个组件只能调用一次 Start;如果每次测试都需要新的 DriverTestRealm,则必须使用 RealmBuilder。
与驱动程序交互
DriverTestRealm 组件从 DriverManager 公开 /dev/
目录,测试代码将通过此目录与驱动程序进行交互。/dev/
目录与正在运行的系统上的 devfs 相同。例如,如果测试添加模拟输入设备,该设备会显示在 /dev/class/input-report/XXX
处。
包括司机
默认情况下,DriverTestRealm 组件会从自己的软件包中加载驱动程序。测试作者必须确保将预期加载的测试软件包中包含所有驱动程序。
绑定驱动程序
默认情况下,DriverTest Realm 的根驱动程序是测试父驱动程序。这意味着,如需绑定驱动程序,您应该创建一个绑定到测试父级的模拟驱动程序。这可以通过以下绑定规则来实现:
fuchsia.BIND_PROTOCOL == fuchsia.test.BIND_PROTOCOL.PARENT;
然后,您的模拟驱动程序就可以添加具有正确属性的设备,以便被测驱动程序绑定到模拟驱动程序。
封闭与非封闭
您可以通过两种不同的方式来使用 DriverTestRealm。您既可以采用封闭方式,也可以使用非封闭方式。
封闭
在 Driver Test Realm 的封闭版本中,每个测试都会获得自己的 Driver Test Realm 组件版本。这意味着每个测试都是封闭的,或者与其他测试隔离开来。测试不会共享任何状态,因为每个驾驶员测试领域对该测试都是唯一的。
由于每次测试都必须生成并设置新组件,因此使用封闭的 Driver 测试领域可能会变慢。
非封闭
使用 Driver Test Realm 的非封闭方式是在每个测试实例之间共享一个 Driver Test Realm 子组件。
测试作者需要格外小心,确保在每次测试之间清除其驱动程序的状态,这样各个测试就不会彼此交互。
使用非封闭的驱动程序测试领域可能会快得多,因为每个测试都不需要生成和设置新组件。测试代码可能也会更简单。
DriverTestRealm 示例
以下是在 C++ 和 Rust 中以封闭方式和非封闭方式使用 DriverTestRealm 的一些示例。
如需查看示例,请访问 //examples/drivers/driver_test_realm/。
封闭
测试作者可以使用 RealmBuilder 为每项测试创建新的 DriverTestRealm。DriverFramework 使用 RealmBuilder 中的 DriverTestRealm 提供了一个实用的库。
下面是一个将 DriverTestRealm 与 RealmBuilder 搭配使用的示例 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_hlcpp",
"//sdk/fidl/fuchsia.io:fuchsia.io_hlcpp",
"//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/async-loop",
"//zircon/system/ulib/async-loop:async-loop-cpp",
"//zircon/system/ulib/async-loop:async-loop-default",
"//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 = "2021"
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-async",
"//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) {
// Create and build the realm.
auto realm_builder = component_testing::RealmBuilder::Create();
driver_test_realm::Setup(realm_builder);
auto realm = realm_builder.Build(dispatcher());
// Start DriverTestRealm.
fidl::SynchronousInterfacePtr<fuchsia::driver::test::Realm> driver_test_realm;
ASSERT_EQ(ZX_OK, realm.component().Connect(driver_test_realm.NewRequest()));
fuchsia::driver::test::Realm_Start_Result realm_result;
ASSERT_EQ(ZX_OK, driver_test_realm->Start(fuchsia::driver::test::RealmArgs(), &realm_result));
ASSERT_FALSE(realm_result.is_err()) << zx_status_get_string(realm_result.err());
// Connect to dev.
fidl::InterfaceHandle<fuchsia::io::Node> dev;
ASSERT_EQ(ZX_OK, realm.component().Connect("dev-topological", dev.NewRequest().TakeChannel()));
fbl::unique_fd root_fd;
ASSERT_EQ(ZX_OK, fdio_fd_create(dev.TakeChannel().release(), root_fd.reset_and_get_address()));
// Wait for driver.
zx::result channel =
device_watcher::RecursiveWaitForFile(root_fd.get(), "sys/test/sample_driver");
ASSERT_EQ(channel.status_value(), ZX_OK);
// Turn the connection into FIDL.
fidl::WireSyncClient client(
fidl::ClientEnd<fuchsia_hardware_sample::Echo>(std::move(channel.value())));
// Send a FIDL request.
constexpr std::string_view sent_string = "hello";
fidl::WireResult result = client->EchoString(fidl::StringView::FromExternal(sent_string));
ASSERT_EQ(ZX_OK, result.status());
ASSERT_EQ(sent_string, result.value().response.get());
}
Rust
#[fasync::run_singlethreaded(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(())
}
#[fasync::run_singlethreaded(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
以查看已加载的驱动程序。
首先,正确设置构建规则。测试软件包需要包含 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",
"//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::SetTags({"driver_test_realm_test"});
// Connect to DriverTestRealm.
auto client_end = component::Connect<fuchsia_driver_test::Realm>();
if (!client_end.is_ok()) {
FX_SLOG(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_SLOG(ERROR, "Failed to call to Realm:Start", FX_KV("status", wire_result.status()));
return 1;
}
if (wire_result.value().is_error()) {
FX_SLOG(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 = "2021"
source_root = "test.rs"
sources = [ "test.rs" ]
deps = [
"//sdk/lib/device-watcher/rust",
"//sdk/lib/driver_test_realm/simple",
"//src/lib/fuchsia-async",
"//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
#[fasync::run_singlethreaded(test)]
async fn test_driver() -> Result<()> {
let dev = fuchsia_fs::directory::open_in_namespace("/dev", fuchsia_fs::OpenFlags::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 部分。