驅動程式庫整合測試架構
DriverTestRealm 是整合測試架構,可在密封環境中執行驅動程式。這對於驅動程式庫程式作者能測試其驅動程式,以及讓系統開發人員執行使用特定驅動程式庫堆疊的整合測試,因此非常實用。DriverTestRealm 提供所有驅動程式架構 API 的密封版本,且提供與執行系統幾乎相同的環境。
DriverTestRealm 適用於整合測試,如果是輕量級單元測試架構,請改用模擬 DDK。
DriverTestRealm 總覽
DriverTestRealm 是測試可存取的元件。這個元件包含所有 DriverFramework 的元件,例如 DriverManager 和 DriverIndex。並模擬這些元件所需的所有功能。
DriverTestRealm 會公開用於啟動 DriverTestRealm 的 fuchsia.driver.test/Realm
通訊協定。Start 函式會使用引數來設定以下項目,例如載入哪些驅動程式、根驅動程式庫,以及 DriverManager 如何關閉。每個元件只能呼叫一次啟動;如果每項測試都需要新的 DriverTestRealm,則必須使用 RealmBuilder。
與駕駛人互動
DriverTestRealm 元件會從 DriverManager 公開 /dev/
目錄,這是測試程式碼與驅動程式互動的方式。/dev/
目錄與執行系統中的 devfs 有相同的運作方式。舉例來說,如果測試新增了模擬輸入裝置,就會在 /dev/class/input-report/XXX
中顯示。
包括駕駛員
根據預設,DriveTestRealm 元件會從其套件載入驅動程式。測試作者必須納入測試套件中預期要載入的所有驅動程式。
繫結驅動程式
根據預設,DriveTest 領域的根驅動程式庫是測試父項驅動程式。也就是說,如要繫結驅動程式庫,您必須建立繫結至測試父項的模擬驅動程式庫。這可以滿足下列繫結規則:
fuchsia.BIND_PROTOCOL == fuchsia.test.BIND_PROTOCOL.PARENT;
接著,模擬驅動程式庫可以新增具備正確屬性的裝置,讓受測的驅動程式庫繫結至模擬驅動程式庫。
密封與不透明
DriverTestRealm 的使用方式有兩種。使用方式具備固有或非金屬質感。
密封
在驅動程式測試領域中,每個測試都會取得自己的驅動程式測試領域元件自己的版本。這表示每項測試均為其他測試的密封式或隔離測試。測試不會共用任何狀態,因為每個驅動程式測試領域對於該測試而言都是獨一無二的。
使用密封的驅動程式測試領域可能會降低執行速度,因為每次測試都必須產生及設定新元件。
非密封素
使用驅動程式測試領域的非典型做法,就是讓每個測試執行個體共用一個驅動程式測試領域子項元件。
測試作者必須特別謹慎,確認每次測試之間都會清除其驅動程式狀態,以確保個別測試不會彼此互動。
由於每項測試不需要產生及設定新元件,使用非密封的驅動程式測試領域可能會更快許多。測試程式碼也可能比較簡單。
DriverTestRealm 範例
以下範例說明如何在 C++ 和 Rust 中,使用 DriverTestRealm 拼湊或不美觀的方式使用。
如要查看範例,請前往 //examples/drivers/driver_test_realm/。
密封
測試作者可以使用 RealmBuilder 為每項測試建立新的 DriverTestRealm。DriverFramework 使用 DriverTestRealm in RealmBuilder 提供實用的程式庫。
以下是將 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- 每個元件只能呼叫一次啟動。如果您想為每個測試建立新的 DriverTestRealm,請參閱 RealmBuilder 部分。