Fuchsia 测试运行程序框架

借助 Fuchsia 组件框架,开发者可以使用各种语言和运行时创建组件。Fuchsia 自己的代码为组件使用了各种编程语言,包括 C/C++、Rust、Dart 和 Go。

测试运行程序框架使用组件框架运行程序作为各种测试运行时之间的集成层,并使用通用的 Fuchsia 协议来启动测试和接收结果。这种设计可实现包容性设计,一方面,允许开发者提供自己选择的语言和测试框架;另一方面,允许开发者在各种系统上以不同的硬件为目标构建和测试 Fuchsia。

测试管理器

test_manager 组件负责在 Fuchsia 设备上运行测试。测试管理器公开 fuchsia.test.manager.RunBuilder 协议,以允许启动测试套件。

每个测试套件都作为测试管理器的子级启动。测试套件由测试管理器提供,使它们能够在完成工作的同时保持测试与系统其他部分之间的隔离。例如,封闭测试可以记录消息,但无法与沙盒外的实际系统资源进行交互。测试管理器仅使用测试领域中的一项功能,即测试套件公开的控制器协议。这样做是为了确保封闭性(测试结果不受其预期沙盒以外的任何内容影响)和隔离性(测试既不会相互影响,也不会影响系统的其余部分)。

将测试管理器控制器本身提供给系统中的其他组件,以便将测试执行与各种开发者工具集成。然后,可以使用 fx testffx 等工具启动测试。

测试套件协议

测试管理器使用测试套件协议 fuchsia.test.Suite 来控制测试,例如调用测试用例和收集其结果。

测试作者通常不需要实现此协议。而是依靠测试运行程序来执行此操作。例如,您可以使用 GoogleTest 框架以 C++ 编写一项测试,然后在组件清单中使用 gtest_runner 与测试运行程序框架集成。

测试运行程序

一种语言和运行时包容的框架

测试运行程序是测试运行程序框架与开发者用于编写测试的通用语言和框架之间的可重复使用的适配器。他们会代表测试作者实现 fuchsia.test.Suite 协议,从而允许开发者针对其语言和所选框架编写惯用测试。

简单单元测试的组件清单可以由构建规则生成。为 v2 测试生成的组件清单将根据其构建定义包含相应的测试运行程序。例如,依赖于 GoogleTest 库的测试可执行文件将在生成的清单中包含 GoogleTest 运行程序

测试运行程序清单

以下测试运行程序目前已正式发布:

GoogleTest 运行程序

使用 GoogleTest 框架以 C/C++ 编写的测试的运行程序。 对于使用 GoogleTest 编写的所有测试,请使用此方法。

支持常见的 GoogleTest 功能,如停用测试、仅运行指定的测试、多次运行同一测试等。从测试中捕获标准输出、标准错误和日志。

如需使用此运行程序,请将以下代码添加到组件清单中:

{
    include: [ "//src/sys/test_runners/gtest/default.shard.cml" ]
}

默认情况下,GoogleTest 测试用例按顺序运行(一次一个测试用例)。

GoogleTest (Gunit) 运行程序

使用 GUnit 框架以 C/C++ 编写的测试的运行程序。对于使用 GoogleTest 的 gUnit 变种编写的所有测试,请使用此方法。

支持常见的 GoogleTest 功能,如停用测试、仅运行指定的测试、多次运行同一测试等。从测试中捕获标准输出、标准错误和日志。

如需使用此运行程序,请将以下代码添加到组件清单中:

{
    include: [ "sys/testing/gunit_runner.shard.cml" ]
}

默认情况下,测试用例按顺序运行(一次一个测试用例)。

Rust 运行程序

运行使用 Rust 编程语言编写的测试并遵循 Rust 测试习语的运行程序。将此用于所有惯用的 Rust 测试(即,包含设置了 [cfg(test)] 属性的模块的测试)。

支持常见的 Rust 测试功能,如停用测试、仅运行指定的测试、多次运行同一测试等。从测试中捕获标准输出、标准错误和日志。

如需使用此运行程序,请将以下代码添加到组件清单中:

{
    include: [ "//src/sys/test_runners/rust/default.shard.cml" ]
}

默认情况下,Rust 测试用例并行运行,一次最多 10 个用例。

Go 测试运行程序

运行使用 Go 编程语言编写的测试并遵循 Go 测试习语的运行程序。对于使用 import "testing" 以 Go 编写的所有测试,请使用此方法。

支持常见的 Go 测试功能,如停用测试、仅运行指定的测试、多次运行同一测试等。从测试中捕获标准输出、标准错误和日志。

如需使用此运行程序,请将以下代码添加到组件清单中:

{
    include: [ "//src/sys/test_runners/gotests/default.shard.cml" ]
}

默认情况下,Go 测试用例并行运行,一次最多 10 个案例。

ELF 测试运行程序

最简单的测试运行程序 - 它会等待程序终止,然后在程序返回零时报告测试已通过,或针对任何非零返回值报告测试失败。

如果您的测试是以 ELF 程序(例如,使用 C/C++ 编写的可执行文件)实现的,请使用此测试运行程序,但它不使用现有运行程序支持的通用测试框架,并且您不想实现定制的测试运行程序。

如需使用此运行程序,请将以下代码添加到组件清单中:

{
    include: [ "sys/testing/elf_test_runner.shard.cml" ]
}

如果您使用的是树内单元测试 GN 模板,并且尚未使用具有专用测试运行程序的测试框架,请将以下代码添加到 build 依赖项:

fuchsia_unittest_package("my-test-packkage") {
    // ...
    deps = [
        // ...
        "//src/sys/testing/elftest",
    ]
}

控制测试用例的并行执行

使用 fx test 启动测试时,它们可以按顺序运行每个测试用例,也可以并行运行多个测试用例,直至达到给定上限。默认的并行行为由测试运行程序确定。如需手动控制并行运行的测试用例的数量,请使用测试规范:

fuchsia_test_package("my-test-pkg") {
  test_components = [ ":my_test_component" ]
  test_specs = {
    # control the parallelism
    parallel = 10
  }
}

多次运行测试

如需多次运行测试,请使用以下命令:

 fx test --count=<n> <test_url>

如果迭代超时,将不再执行进一步的迭代。

传递参数

您可以使用 fx test 向测试传递自定义参数:

fx test <test_url> -- <custom_args>

单个测试运行程序对以下自定义标志有限制:

GoogleTest 运行程序

请注意以下已知的行为变更:

--gtest_break_on_failure:由于每个测试用例在不同进程中执行,此标志将不起作用。

以下标志受到限制,如果传递任何标志,则测试会失败,因为 fuchsia.test.Suite 提供了等效的功能来取代这些标志。

  • --gtest_filter - 请改用:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。系统将执行与任何给定 glob 模式匹配的测试。

  • --gtest_another_run_disabled_tests - 请改用:
 fx test --also-run-disabled-tests <test_url>
  • --gtest_repeat - 请参阅多次运行测试
  • --gtest_output - 不支持发出 gtest json 输出。
  • --gtest_list_tests - 不支持列出测试用例。

GoogleTest (Gunit) 运行程序

请注意以下已知的行为变更:

--gunit_break_on_failure:由于每个测试用例在不同进程中执行,此标志将不起作用。

以下标志受到限制,如果传递任何标志,则测试会失败,因为 fuchsia.test.Suite 提供了等效的功能来取代这些标志。

  • --gunit_filter - 请改用:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。系统将执行与任何给定 glob 模式匹配的测试。

  • --gunit_another_run_disabled_tests - 请改用:
 fx test --also-run-disabled-tests <test_url>
  • --gunit_repeat - 请参阅多次运行测试
  • --gunit_output - 不支持发出 gtest json/xml 输出。
  • --gunit_list_tests - 不支持列出测试用例。

Rust 运行程序

以下标志受到限制,如果传递任何标志,则测试会失败,因为 fuchsia.test.Suite 提供了等效的功能来取代这些标志。

  • <test_name_matcher> - 请改用:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。系统将执行与任何给定 glob 模式匹配的测试。

  • --nocapture - 默认输出输出。
  • --list - 不支持列出测试用例。

Go 测试运行程序

请注意以下已知的行为变更:

-test.failfastboot:由于每个测试用例都是在不同的进程中执行,因此此标志将仅影响子测试。

以下标志受到限制,如果通过了任何标志,测试会失败,因为 fuchsia.test.Suite 提供了等效的功能来取代这些标志

  • -test.run - 请改用:
 fx test --test-filter=<glob_pattern> <test_url>

可以多次指定 --test-filter。系统将执行与任何给定 glob 模式匹配的测试。

不依赖于运行时、具有运行时包容性的测试框架

Fuchsia 旨在实现包容性,例如,开发者可以用自己选择的语言和运行时创建组件(及其测试)。测试运行程序框架本身在设计上与语言无关,其单个测试运行程序专门用于特定的编程语言或测试运行时,因此具有包容性。任何人都可以创建和使用新的测试运行程序。

创建新的测试运行程序相对容易,并且可以在不同运行程序之间共享代码。例如,GoogleTest 运行程序和 Rust 运行程序共享与启动 ELF 二进制文件相关的代码,但在将命令行参数传递给测试以及解析测试结果的代码方面有所不同。

临时存储

如需在测试中使用临时存储空间,请将以下代码添加到组件清单中:

{
    include: [ "//src/sys/test_runners/tmp_storage.shard.cml" ]
}

在运行时,您的测试将拥有对 /tmp 的读写权限。测试开始时,此目录的内容将为空,并在测试完成后删除。

未指定自定义清单并且依赖构建系统来生成其组件清单的测试可以添加以下依赖项

fuchsia_unittest_package("foo-tests") {
  deps = [
    ":foo_test",
    "//src/sys/test_runners:tmp_storage",
  ]
}

导出自定义文件

如需从测试中导出自定义文件,请使用 custom_artifacts 存储功能。测试结束时,系统会复制 custom_artifacts 的内容。

如需在测试中使用 custom_artifacts,请将以下内容添加到组件清单中:

{
    use: [
        {
            storage: "custom_artifacts",
            rights: [ "rw*" ],
            path: "/custom_artifacts",
        },
    ],
}

在运行时,您的测试将拥有对 /custom_artifacts 的读写权限。测试开始时,此目录的内容将为空,并在测试完成后删除。

请参阅自定义工件测试示例。如需运行该工具,请将 //examples/tests/rust:tests 添加到您的 build 中,然后运行以下命令:

fx test --ffx-output-directory <output-dir> custom_artifact_user

测试结束后,<output-dir> 将包含测试生成的 artifact.txt 文件。

封闭性

如果某个测试符合以下条件,则测试为封闭测试:

  1. 使用提供测试根的父级中的任何功能。
  2. 无法解析测试软件包之外的任何组件。

除非另有明确说明,否则默认情况下,这些测试都是封闭的。

用于测试的封闭功能

有一些功能可供所有测试使用,并且不违反测试封闭性:

协议 说明
fuchsia.boot.WriteOnlyLog 写入内核日志
fuchsia.logger.LogSink 写入 Syslog
fuchsia.process.Launcher 从测试软件包中启动子进程
fuchsia.diagnostics.ArchiveAccessor 读取测试中组件的诊断输出

之所以保留这些功能的封闭性,是因为这些功能经过精心挑选,不会允许测试影响测试领域以外或其他测试的系统组件的行为。

如需使用这些功能,应在测试的清单文件中添加一个 use 声明:

// my_test.cml
{
    use: [
        ...
        {
            protocol: [
              "fuchsia.logger.LogSink"
            ],
        },
    ],
}

测试还提供了一些默认的存储功能,这些功能在测试执行完毕后会被销毁。

存储容量 说明 路径
data 隔离的数据存储目录 /data
cache 隔离的缓存存储目录 /cache
tmp 隔离的内存中的临时存储目录 /tmp

在测试的清单文件中添加 use 声明,以便使用这些功能。

// my_test.cml
{
    use: [
        ...
        {
            storage: "data",
            path: "/data",
        },
    ],
}

该框架还为所有组件提供了一些功能,可供测试组件根据需要使用。

封闭组件分辨率

封闭测试组件是在利用封闭组件解析器的领域发布的。此解析器不允许解析测试软件包之外的网址。这是强制执行封闭性的必要条件,因为我们不希望系统或关联的软件包服务器中任意组件的可用性影响测试结果。

尝试解析不在测试软件包中的组件时,系统将显示 PackageNotFound 错误,并在 syslog 中显示以下消息:

failed to resolve component fuchsia-pkg://fuchsia.com/[package_name]#meta/[component_name]: package [package_name] is not in the set of allowed packages...

您可以将测试所依赖的任何组件添加到测试软件包中,从而避免此错误。如需查看具体操作方法的示例,请参阅此 CL,或者使用子软件包

# BUILD.gn
import("//build/components.gni")


fuchsia_test_package("simple_test") {
  test_components = [ ":simple_test_component" ]
  subpackages = [ "//path/to/subpackage:subpackage" ]
}
 // test.cml
 {
...
    children: [
        {
            name: "child",
            url: "subpackage#meta/subpackaged_component.cm",
        },
    ],
...
}

如需查看使用子软件包的示例,请参阅此 CL

// my_component_test.cml

{
...

    facets: {
        "fuchsia.test": {
            "deprecated-allowed-packages": [ "non_hermetic_package" ],
        },
    },
...
}

非封闭测试

这些测试可以访问测试领域之外的一些预定义功能。非封闭测试从测试领域外部访问的功能称为系统功能。

如需使用系统功能,测试必须将自身明确标记为在非封闭领域运行,如下所示。

# BUILD.gn (in-tree build rule)

fuchsia_test_component("my_test_component") {
  component_name = "my_test"
  manifest = "meta/my_test.cml"
  deps = [ ":my_test_bin" ]

  # This runs the test in "system-tests" non-hermetic realm.
  test_type = "system"
}

与构建规则集成后,测试可以按如下方式执行:

fx test <my_test>

或者对于树外开发者

ffx test run --realm <realm_moniker> <test_url>

在上面的示例中,其中 realm_moniker 应替换为 /core/testing:system-tests

test_type 的可能值:

说明
chromium Chromium 测试领域
ctf CTF 测试领域
device 设备测试
drm DRM 测试
starnix Starnix 测试
system_validation 系统验证测试
system 旧版非封闭领域,有权访问某些系统功能。
test_arch 测试架构测试
vfs-compliance VFS 合规性测试
vulkan Vulkan 测试

了解如何创建您自己的测试领域。

非封闭的旧版测试领域

这些是我们在推出测试管理器即服务之前创建的旧版测试领域。我们正在移植这些领域如果您的测试依赖于其中一个领域,则应将自己明确标记为在旧版领域运行,如下所示。

// my_component_test.cml

{
    include: [
        // Select the appropriate test runner shard here:
        // rust, gtest, go, etc.
        "//src/sys/test_runners/rust/default.shard.cml",

        // This includes the facet which marks the test type as 'starnix'.
        "//src/devices/testing/starnix_test.shard.cml",
    ],
    program: {
        binary: "bin/my_component_test",
    },
    
    use: [
        {
            protocol: [ "fuchsia.vulkan.loader.Loader" ],
        },
    ],
}

分片在清单文件中包含以下 Facet:

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [ "//src/starnix/tests/starnix_test_common.shard.cml" ],
    expose: [
        {
            protocol: "fuchsia.test.Suite",
            from: "self",
        },
    ],
}

fuchsia.test.type 的可能值:

说明
hermetic 封闭的王国
chromium-system Chromium 系统测试领域
google Google 测试领域

受限日志

默认情况下,如果测试记录了严重级别为 ERROR 或更高的消息,则测试将失败。如需了解详情,请参阅此指南

性能

在编写用于启动进程的测试运行程序时,该运行程序需要提供库加载器实现。

测试运行程序通常会在单独的进程中启动单独的测试用例,在测试用例之间实现更大程度的隔离。不过,这可能会显著降低性能。为了缓解此问题,上文列出的测试运行程序使用缓存加载器服务,这样可以减少每次启动进程所产生的额外开销。

测试角色

测试领域中的组件在测试中可能扮演各种角色,如下所示:

  • 测试根:测试组件树顶部的组件。测试的网址会标识此组件,并且测试管理器将调用此组件公开的 fuchsia.test.Suite 来驱动测试。
  • 测试驱动程序:实际运行测试并实现(直接或通过测试运行程序 fuchsia.test.Suite 协议)的组件。请注意,测试驱动程序和测试根可以(但不一定是)同一个组件:例如,测试驱动程序可能是重新公开其 fuchsia.test.Suite 的测试根子组件。
  • 功能提供程序:一种组件,用于提供测试将以某种方式执行的功能。该组件可以提供测试功能的“虚构”实现,也可以提供与正式版所使用的功能等效的“实际”实现。
  • 被测组件:执行要测试的某些行为的组件。该组件可能与生产环境中的组件或专为测试(用于模拟生产环境行为)而编写的组件相同。

问题排查

本部分包含您在使用测试运行程序框架开发测试组件时可能会遇到的常见问题。如果您的某个测试组件无法运行,您可能会看到来自 fx test 的如下错误:

Test suite encountered error trying to run tests: getting test cases
Caused by:
    The test protocol was closed. This may mean `fuchsia.test.Suite` was not configured correctly.

如需解决此问题,请探索以下选项:

测试使用了错误的测试运行程序

如果您在测试枚举期间遇到此错误,那么您可能使用了错误的测试运行程序。

例如:您的 Rust 测试文件可能在不使用 Rust 测试框架的情况下运行测试(即,它是一个简单的 Rust 二进制文件,拥有自己的主函数)。在这种情况下,请将您的测试清单文件更改为使用 elf_test_runner

详细了解内置测试运行程序

测试未能向测试管理器公开 fuchsia.test.Suite

当测试根未能从测试根公开 fuchsia.test.Suite 时,就会发生这种情况。一个简单的解决方法是添加 expose 声明:

// test_root.cml
expose: [
    ...
    {
        protocol: "fuchsia.test.Suite",
        from: "self",  // If a child component is the test driver, put `from: "#driver"`
    },
],

测试驱动程序未能向根目录公开 fuchsia.test.Suite

如果未正确公开 fuchsia.test.Suite 协议,您的测试可能会失败,并显示类似于以下内容的错误:

ERROR: Failed to route protocol `/svc/fuchsia.test.Suite` from component
`/test_manager/...`: An `expose from #driver` declaration was found at `/test_manager/...`
for `/svc/fuchsia.test.Suite`, but no matching `expose` declaration was found in the child

如果测试驱动程序和测试根是不同的组件,则测试驱动程序还必须向其父项(测试根)公开 fuchsia.test.Suite

如需解决此问题,请确保测试驱动程序组件清单包含以下 expose 声明:

// test_driver.cml
expose: [
    ...
    {
        protocol: "fuchsia.test.Suite",
        from: "self",
    },
],

测试驱动程序不使用测试运行程序

测试驱动程序必须使用与编写测试所用的语言和测试框架相对应的适当测试运行程序。例如,Rust 测试的驱动程序需要以下声明:

// test_driver.cml
include: [ "//src/sys/test_runners/rust/default.shard.cml" ]

此外,如果测试驱动程序是测试根目录的子级,您需要将其提供给驱动程序:

// test_root.cml
offer: [
    {
        runner: "rust_test_runner",
        to: [ "#driver" ],
    },
],

深入阅读