Bazel 测试

范围

本文档介绍了如何定义、构建和运行仅存在于 Bazel 图中的测试。

Fuchsia 支持满足以下要求的 Bazel 宿主测试:

  • 这些测试必须仅使用 Bazel 构建;也就是说,它们不依赖于之前 Ninja 调用生成的制品。

  • 这些测试仅包含可在兼容的宿主系统上运行的二进制文件和运行时文件,该宿主系统未连接到任何 Fuchsia 设备或模拟器。

Fuchsia 还支持使用 fx_test() 声明的 Bazel 设备测试,这些测试可在 Fuchsia 设备或模拟器上运行。这些模块同样要求只能使用 Bazel 构建,此外还要求:

  • 除了 environments(默认为 build 的默认测试环境)、build_only、max_log_severity(默认为 WARN)和 test_type(用于在非封闭测试领域(例如 "starnix" 或 "system")中运行)之外,测试规范字段(超时时间等)目前还无法按测试进行自定义。

fxbug.dev/564574581 跟踪 Bazel 中针对目标测试的剩余工作。

在 Bazel 中定义 Fuchsia 主机测试

常规 Bazel 测试规则(例如 rustc_test、go_test)与 Fuchsia 测试运行程序不兼容。它们不会向 fx test 显示,也不会由基础设施构建者运行。

因此,如需在 Bazel 中定义与 Fuchsia 兼容的主机测试,请执行以下操作:

  • 使用 //build/bazel/rules/host_tests:host_<xxx>_test.bzl 中的特定语言主机测试封装容器,例如 host_rustc_test()、host_go_test() 等。

    这些规则旨在替代常规 Bazel 测试规则,并提供 test_data、test_args 等额外实参,以便 Fuchsia 测试运行程序能够使用正确的运行时环境和实参来调用它们。

  • 只有在没有适用于您的测试的特定语言封装容器时,才使用 //build/bazel/rules/host_tests:host_test.bzl 中的通用 host_test() 封装容器。例如:

    load("//build/bazel/rules/host_tests:host_test.bzl", "host_test")
    load("@rules_sh//sh:sh_binary.bzl", "sh_binary")
    
    sh_binary(
        name = "test_bin",
        ...
    )
    
    host_test(
        name = "shell_test",
        binary = ":test_bin",
    )
    
  • 请勿假设测试二进制文件的执行位置(例如来自 out/default),因为它是从确切路径不可预测的目录启动的。

    Bazel 主机测试必须是密封的,并且必须像 GN 主机测试一样明确列出其所有运行时依赖项。可以通过以下机制之一提供运行时依赖项:

    • 作为 test_data 对 host_test_data_files() 或 host_test_data_map() 目标的依赖项(请参阅 //build/bazel/rules/host_tests:host_test_data.bzl)。这些依赖项使文件在运行时可位于相对于测试执行目录的固定位置,类似于 GN host_test_data() 模板。

    • 作为 host_xxx_test() 目标上的 data 属性。 在这种情况下,文件将添加到测试的 runfiles 中,并且可以使用 runfiles 库进行访问。

  • 使用 test_suite() 而不是 filegroup() 来定义测试组。 测试套件可以依赖于测试和其他测试套件。

将 Bazel 主机测试导出到 fx test 和基础设施构建器

仅定义 host_xxx_test() 目标不会使其对 fx test 和(更重要的是)基础设施构建者可见。

如需让 fx test 和基础架构构建器看到 Bazel 主机测试,请在 GN 中编写一个 bazel_test_suite 目标,其中列出了一个或多个 Bazel 主机测试或套件的 Bazel 标签,并将该目标添加到 GN 中的“tests”组。例如:

//src/foo/bar/BUILD.bazel:

load("//build/bazel/platforms:constraints.bzl", "HOST_OS_CONSTRAINTS")

test_suite(
  name = "host_tests",
  host_tests = [
    "//src/foo/bar/lib:lib_tests",
    "//src/foo/bar/util:util_tests",
  ]
  target_compatible_with = HOST_OS_CONSTRAINTS,
)

//src/foo/bar/BUILD.gn:

import("//build/bazel/bazel_test_suite.gni")

bazel_test_suite("bazel_tests") {
  host_tests = [
    # This references the target in BUILD.bazel.
    "//src/foo/bar:host_tests",
  ]
}

group("tests") {
  deps = [
    ":bazel_tests",
    ...
  ]
}

这会导致 Bazel 测试的条目显示在 out/default/tests.json 中。如有疑问,请在 fx set 或 fx gen 之后检查此文件的内容。Bazel 测试的 label 字段以 @ 开头。

在开发期间,可以在定义测试目标后构建并运行它们,然后再按上述方式导出它们。请参阅下文中的直接使用 Bazel 运行测试部分。

最佳做法

建议避免 Bazel test_suite 目标深度嵌套。请改为针对每个源代码子区域(例如宿主工具或 Fuchsia 组件)仅使用一个 test_suite Bazel 目标和一个对应的 bazel_test_suite GN 目标。

在 Bazel 中定义 Fuchsia 设备测试

设备测试是 Fuchsia 软件包中的组件,与 GN build 中的组件完全一样。使用 fx_test_component() 声明测试组件,使用 fx_package() 将其打包,并使用 fx_test() 将其公开为测试:

//src/foo/bar/BUILD.bazel:

load("//build/bazel/rules/packages:fx_packaged_binary.bzl", "fx_packaged_binary")
load(
    "//build/bazel/rules/components:fx_component.bzl",
    "fx_component_manifest",
    "fx_test_component",
)
load("//build/bazel/rules/packages:fx_package.bzl", "fx_package")
load("//build/bazel/rules/testing:fx_test.bzl", "fx_test")

fx_packaged_binary(
    name = "bar_test_packaged_bin",
    testonly = True,
    binary = ":bar_test_bin",
    binary_name = "bar_test",
)

fx_component_manifest(
    name = "bar_test_manifest",
    testonly = True,
    component_name = "bar_test",
    includes = ["//src/sys/test_runners/gtest:default.shard.cml"],
    manifest = "meta/bar_test.cml",
)

fx_test_component(
    name = "bar_test_component",
    compiled_manifest = ":bar_test_manifest",
    component_name = "bar_test",
    deps = [":bar_test_packaged_bin"],
)

fx_package(
    name = "bar_tests_package",
    package_name = "bar_tests",
    test_components = [":bar_test_component"],
)

fx_test(
    name = "bar_tests",
    package = ":bar_tests_package",
    # Optional: defaults to the build's default test environments when omitted.
    environments = [
        "//build/testing/environments:aemu_env",
        "//build/testing/environments:nuc11_env",
    ],
)

test_components 中的每个组件都会成为一个单独的测试,其名称是其软件包网址,例如 fuchsia-pkg://fuchsia.com/bar_tests#meta/bar_test.cm。

如需自定义测试运行的目标环境,请将 environments 作为环境目标列表传递给 fx_test()。//build/testing/environments 在 //build/testing/environments.gni 中为每个单独的环境设置了一个目标,且名称相同(例如 :emu_env 或 :nuc11_env)。如果省略 environments,测试将在 build 的默认测试环境 (GN 的 basic_envs) 中运行,与未设置 environments 的 GN 测试完全相同。如需构建测试但不运行,请改为设置 build_only = True。

如需向其中一个环境添加基础架构设置(例如标记),请使用 fx_test_environment_variant():

load(
    "//build/bazel/rules/testing:fx_test_environment.bzl",
    "fx_test_environment_variant",
)

fx_test_environment_variant(
    name = "nuc11_isolated_env",
    base = "//build/testing/environments:nuc11_env",
    env_tags = ["e2e-isolated"],
)

新环境应该很少;大多数测试都应使用 //build/testing/environments 中的现有环境之一。如果需要新环境,请将其添加到 //build/testing/environments.gni 和 //build/testing/environments/BUILD.bazel 中,而不是使用 fx_test_environment() 在测试旁边定义它。环境的 Swarming 维度是与基础架构的接口,因此它们会保留在一个位置。

如需在非封闭式测试 realm(例如 "starnix"、"system" 或 "vulkan")中运行测试,请将 test_type 传递给 fx_test_component()(或传递给 fx_test() 以应用于软件包中的所有测试组件)。有效的 test_type 值与 //build/components/fuchsia_test_component.gni 中的 type_moniker_map 相匹配。

将 Bazel 设备测试导出到 fx test 和基础架构构建器

与主机测试一样,设备测试必须列在 GN bazel_test_suite() 目标中,并使用 target_tests 参数而不是 host_tests:

//src/foo/bar/BUILD.gn:

import("//build/bazel/bazel_test_suite.gni")

bazel_test_suite("bazel_tests") {
  target_tests = [
    # This references the target in BUILD.bazel.
    "//src/foo/bar:bar_tests",
  ]
}

设备测试与主机测试分开跟踪,因为它们是在 fuchsia_platform Bazel 配置中(而不是在主机配置中)查询和构建的。

除了 tests.json 条目之外,此命令还会写入 ${root_build_dir}/bazel_test_packages.list,其中列出了 build 图中所有 Bazel 测试软件包的软件包清单。完整的 fx build 调用会将此列表合并到 all_package_manifests.list 中,并将其软件包发布到 amber-files 中,而 fx test 在按需构建特定测试目标时也会直接合并此列表。

运行 Fuchsia Bazel 设备测试

导出后,通过软件包网址或软件包名称运行 Bazel 设备测试,与运行 GN 设备测试的方式相同:

fx test fuchsia-pkg://fuchsia.com/bar_tests#meta/bar_test.cm

fx test 通过直接调用 Bazel (作为 fx build --fuchsia_platform bazel_label) 来构建测试,然后在运行测试之前发布 bazel_test_packages.list 中列出的软件包清单。需要运行中的软件包服务器和已连接的设备或模拟器,这与 GN 设备测试完全相同。

运行 Fuchsia Bazel 主机测试

本地测试

使用 fx test 运行测试

如需使用 fx test 运行 Bazel 宿主测试,必须按照上文将 Bazel 宿主测试导出到 fx test 和基础架构构建器部分中的说明导出这些测试。

导出测试后,使用 fx test --host <name_or_label> 在本地构建并运行 Bazel 主机测试,与 GN 定义的主机测试相同。

唯一的区别在于,测试将通过直接调用 Bazel 按需构建,完全跳过 Ninja。

请注意,使用 fx test 时:

  • <name_or_label> 将接受模糊匹配,如果存在歧义,fx test 将报告多个候选对象。

  • <name_or_label> 无法引用 Bazel test_suite() 标签,就像它无法引用 GN group() 标签一样。

如需详细了解如何使用 fx test,请参阅 fx 测试用户指南。

直接使用 fx bazel test 运行测试

您可以使用 fx bazel test --config=host <target_pattern> 直接通过 Bazel 在本地构建和运行测试。这不需要额外的管道来使测试对 fx test 可见(如上一部分所述),并且具有以下优势:

  • <target_pattern> 是一组 Bazel 目标模式,可匹配“给定软件包中的所有测试目标”,在这种情况下,Bazel 会自动跳过非测试目标。

  • <target_pattern> 可以匹配 test_suite() 标签,这有助于在开发期间反复运行一组主机测试。

  • bazel test 会缓存测试结果,并且仅重新运行自上次调用以来受最新更改影响的测试。

    (使用 --nocache_test_results 标志可停用此功能)。

  • <target_pattern> 可以引用 tests.json 中不可见的标签。 在开发过程中添加新测试时,这会非常方便。

基础架构测试

只有 tests.json 中列出的主机测试才能在基础设施测试机器人上启动。这同样适用于 GN 和 Bazel 主机测试。因此,您必须按照上文中的将 Bazel 主机测试导出到 fx test 和基础架构构建器部分中的说明,确保它们在 tests.json 中可见,否则它们将不会在基础架构上持续测试。

Infra 使用 fint 构建 Bazel 测试,该命令在运行 ninja 后直接调用 bazel build,以构建 bazel_test_suite GN 目标中列出的所有 Bazel 测试。

在基础架构测试运行程序上运行 GN 和 Bazel 定义的主机测试时,或者在收集结果时,两者之间没有明确的区别。特别是,基础架构不会使用 bazel test 运行测试。相反,它会获取 bazel build 生成的二进制文件并直接运行,就像处理来自 Ninja 的宿主测试一样。

调试符号传播

使用调试符号编译的主机测试二进制文件必须注册其未剥离的 ELF 二进制文件,以便工具可以符号化回溯并关联 LLVM 代码覆盖率配置文件 (-profile-correlate=binary)。

如需大致了解 Fuchsia 如何管理 Bazel 调试符号,请参阅有关调试符号生成的技术说明。

使用调试符号编写主机测试规则

通用 host_test() 规则在其 FuchsiaHostTestInfo 提供程序中公开了 unstripped_binary 字段:

  • 如果未明确设置 unstripped_binary,host_test() 会检查底层 binary 属性:

    • 如果 binary 提供 DebugPackageInfo(C++ 目标),则使用 DebugPackageInfo.unstripped_file。
    • 如果 binary 提供 CrateInfo(Rust 目标),则使用 CrateInfo.output。
  • 如果测试使用封装脚本启动器(例如 host_rustc_test() 中的 rust_test_parser),则宏 必须通过 unstripped_binary 显式转发原始未剥离的二进制目标:

    host_test(
        name = name,
        binary = wrapper_script,
        unstripped_binary = ":" + binary_name,
        ...
    )
    

传播到基础设施和 debuginfod

当 Bazel 主机测试导出到 GN build 时:

  1. 测试查询:bazel_tests_utils.py 使用 //build/bazel/starlark/FuchsiaHostTestInfo.cquery 运行 bazel cquery,提取所有已注册的主机的 unstripped_binary 的 execroot 路径。

  2. 主机测试清单:路径会相对于 Ninja build 目录进行归一化处理,并写入 ${root_build_dir}/bazel_host_tests.debug_symbols.json。

  3. GN build API 集成://:bazel_host_test_suites 中的 BUILD.gn 附加指向 bazel_host_tests.debug_symbols.json 的 debug_symbol_manifests 元数据,该元数据由 build_api_module("debug_symbols") 目标使用。

  4. Artifactory 上传:在 CI/CQ build 中,Artifactory 会处理 debug_symbols.json 并将未剥离的 ELF 二进制文件上传到云存储空间 / debuginfod 服务器,从而允许 covargs 等工具解析 build ID。

本地开发和 fx coverage

在办公桌上本地运行覆盖率时,未提交的二进制文件具有 debuginfod 服务器上不存在的新生成的 build ID。

为了支持本地符号相关性,fx coverage 运行:

python3 ${FUCHSIA_DIR}/build/bazel/scripts/copy_bazel_debug_symbols.py ${FUCHSIA_BUILD_DIR}

此脚本会读取 bazel_host_tests.debug_symbols.json,计算每个 ELF 二进制文件的 GNU build ID,并将未剥离的二进制文件复制或符号链接到 ${root_build_dir}/.build-id/xx/yyyyyyyy.debug 中。ffx coverage 和 llvm-profdata 等工具随后会通过 .symbol-index.json 解析这些二进制文件。

常见问题解答

Bazel 测试兼容性

如果 test_suite() 中的任何测试(或其任何依赖项)与当前配置(例如,使用 target_compatible_with)不兼容,则在评估套件期间,系统会跳过该测试,而不是导致 build 失败。

在命令行中使用 //... 和 :all 等模式时也是如此。

如需了解详情,请参阅处理不兼容的目标。

运行 test_suite() 或使用目标模式时,开发者必须验证相关测试是否报告为 PASSED(而非 SKIPPED)。