驅動程式庫整合測試架構
DriverTestRealm 是一種整合測試架構,可在密封環境中執行驅動程式。這項功能可供驅動程式庫作者測試驅動程式,以及供系統開發人員執行使用特定驅動程式庫堆疊的整合測試。DriverTestRealm 提供所有 Driver Frameworks API 的密封版本,並提供與執行中系統幾乎相同的環境。
DriverTestRealm 用於整合測試。如要使用輕量級單元測試架構,請改用單元測試程式庫。
DriverTestRealm 總覽
DriverTestRealm 是測試可存取的元件。這個元件包含 DriverFramework 的所有元件,例如 DriverManager 和 DriverIndex。它會模擬這些元件所需的所有功能。

DriverTestRealm 會公開 fuchsia.driver.test/Realm 通訊協定,用來啟動 DriverTestRealm。Start 函式會採用引數,可用於設定載入的驅動程式、根驅動程式庫,以及 DriverManager 的關閉方式等項目。每個元件只能呼叫一次 Start;如果每個測試都需要新的 DriverTestRealm,則必須使用 RealmBuilder。
與駕駛人互動
請參閱「Using Services with DriverTestRealm」。
包括駕駛人
根據預設,DriverTestRealm 元件會從自己的套件載入驅動程式。測試作者必須確保測試套件包含預期載入的所有驅動程式。
繫結驅動程式
根據預設,DriverTest 領域的根驅動程式庫是測試父項驅動程式。也就是說,如要繫結驅動程式庫,您應建立繫結至測試父項的模擬驅動程式庫。您可以使用下列繫結規則完成這項作業:
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 可能會快得多,因為每項測試都不需要產生及設定新元件。測試程式碼也可能更簡單。
DriverTestRealm 範例
以下是使用 DriverTestRealm 密封和非密封的範例, 包括 C++ 和 Rust。
如要查看範例,請前往 //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",
]
}
荒漠油廠
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-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) {
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);
}
荒漠油廠
#[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: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" ]
}
荒漠油廠
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-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);
}
荒漠油廠
#[fasync::run_singlethreaded(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 一節。