DriverTestRealm

驱动程序集成测试框架

DriverTestRealm 是一个集成测试框架, 封闭环境对驾驶员开发者来说,测试驱动程序非常有用, 让系统开发者能够运行使用特定驱动程序堆栈的集成测试。 DriverTestRealm 提供了所有驱动程序框架的封闭版本 API,并提供与运行系统几乎完全相同的环境。

DriverTestRealm 用于集成测试。用于轻量级单元测试 框架, 使用 模拟 DDK

DriverTestRealm 概览

DriverTestRealm 是您的测试可以访问的组件。这个 组件包含 DriverFramework 的所有组件,例如 DriverManager 和 DriverIndex。它会模拟这些组件需要的所有功能。

图:DriverTestRealm 的非封闭设置

DriverTestRealm 公开了 fuchsia.driver.test/Realm 协议 用于启动 DriverTestRealm。Start 函数采用参数 可用于配置要加载哪些驱动程序、 以及 DriverManager 如何关停。每次只能调用 1 次启动 组件;如果每次测试都需要新的 DriverTestRealm,则 RealmBuilder 。

与驾驶员互动

DriverTestRealm 组件公开了 DriverManager 中的 /dev/ 目录, 这是测试代码与驱动程序交互的方式。/dev/ 目录有效 与运行系统上的 devfs 完全相同。例如,如果测试添加一个 模拟输入设备,它将显示在 /dev/class/input-report/XXX 处。

包括司机

默认情况下,DriverTestRealm 组件会从自己的软件包中加载驱动程序。测试 开发者必须在测试软件包中包含他们自己负责的 预计会加载的内容

绑定驱动程序

默认情况下,DriverTest Realm 的根驱动程序是 测试父级驱动程序。也就是说, 要绑定驱动程序,您应该创建一个模拟驱动程序, 测试父级。可以是 完成以下绑定:

fuchsia.BIND_PROTOCOL == fuchsia.test.BIND_PROTOCOL.PARENT;

然后,您的模拟驱动程序便可以添加具有正确属性的设备, 被测驱动程序将绑定到模拟驱动程序。

封闭与非封闭

有两种使用 DriverTestRealm 的方法。它可用于 封闭式或非封闭式

Hermetic

在驾驶员测试区的封闭版本中,每项测试都会获得 自己的 Driver Test Realm 组件版本。这意味着每次测试 是与其他测试封闭或隔离的测试不会分享 状态,因为每个驱动程序测试领域对于该测试都是唯一的。

图:DriverTestRealm 的封闭设置

使用封闭的驾驶员测试领域可能会变慢,因为每次测试都必须 生成和设置新组件。

非封闭

使用 Driver Test Realm 的非封闭方式是让单个驾驶员 在每个测试实例之间共享的测试 Realm 子组件。

图:DriverTestRealm 的非封闭设置

测试作者需要格外小心,确保司机 会在每次测试之间清除状态 彼此交互

使用非封闭的驾驶员测试领域可能会快得多,因为每次测试 不需要生成和设置新组件。测试代码 使其更加简单

DriverTestRealm 示例

以下是以封闭方式和非封闭方式使用 DriverTestRealm 的一些示例, 开发应用。

如需查看示例,请访问 //examples/drivers/driver_test_realm/

Hermetic

测试作者可以使用 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/async-loop",
    "//sdk/lib/async-loop:async-loop-cpp",
    "//sdk/lib/async-loop:async-loop-default",
    "//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 = "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: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 = "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_deprecated(
        "/dev",
        fuchsia_fs::OpenFlags::empty(),
    )?;
    device_watcher::recursive_wait(&dev, "sys/test").await?;
    Ok(())
}

常见问题:

  • 驱动程序没有绑定 <ph type="x-smartling-placeholder">
      </ph>
    • 确保驱动程序包含在软件包中
    • 如果使用的是默认根驱动程序,请确保包含 src/devices/misc/drivers/test-parent
  • 对 /dev/ 的调用挂起 <ph type="x-smartling-placeholder">
      </ph>
    • 确保调用 fuchsia.driver.test/Realm:Start
  • fuchsia.driver.test/Realm:Start 返回 ZX_ERR_ALREADY_BOUND <ph type="x-smartling-placeholder">
      </ph>
    • 每个组件只能调用一次 start。如果您希望每次测试都有新的 DriverTestRealm,请参阅 RealmBuilder 部分。