Bazel 测试

范围

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

Fuchsia 仅支持满足以下要求的 Bazel 主机测试

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

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

fxbug.dev/546173288 跟踪在 Bazel 中支持目标测试的问题。

在 Bazel 中定义 Fuchsia 主机测试

常规 Bazel 测试规则(例如 rustc_testgo_test)与 Fuchsia 测试运行程序不兼容。它们不会对 fx test 可见,也不会由基础设施构建者运行。

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

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

    这些规则旨在替代常规 Bazel 测试规则,并提供 test_datatest_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 setfx gen 之后检查此文件的内容。Bazel 测试的 label 字段以 @ 开头。

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

最佳做法

建议避免 Bazel test_suite 目标过深嵌套。请改为针对每个源代码子区域(例如宿主工具或 Fuchsia 组件)仅使用一个 test_suite Bazel 目标和一个对应的 bazel_test_suite 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 和基础架构 build 服务器部分中的说明,确保这些测试在 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_binaryhost_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_test_suitesBUILD.gn 中附加指向 bazel_host_tests.debug_symbols.jsondebug_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.debugffx coveragellvm-profdata 等工具随后会通过 .symbol-index.json 解析这些二进制文件。

常见问题解答

Bazel 测试兼容性

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

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

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

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