Bazel 测试

范围

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

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

  • 这些测试必须只能使用 Bazel 构建,即它们不依赖于先前 Ninja 调用生成的工件。

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

Fuchsia 还支持 Bazel 设备测试,这些测试使用 fx_test() 声明,并在 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",
    )
    
  • 对于 C/C++ 测试,host_cc_test() 会使用 fx_cc_test_binary() 构建其二进制文件,并且不会向 host_test() 添加任何其他内容。如需同时运行已在主机上打包为设备测试的 fx_cc_test_binary(),请使用 host_test() 将其封装,而不是通过相同来源定义第二个二进制文件,例如在 Fuchsia 和主机工具链中构建的 GN test():

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

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

    • 作为 host_test_data_files() 或 host_test_data_map() 目标的 test_data 依赖项(请参阅 //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 可见,更重要的是,对基础架构构建者不可见。

如需使 Bazel 主机测试对 fx test 和基础架构构建器可见,请在其软件包中公开 "host_tests" test_suite(),将该软件包套件包含在区域聚合 test_suite() 目标中,从同一目录的 BUILD.gn 中的 bazel_test_suite() 引用聚合套件,并将该目标添加到 GN 中的 group("tests")。例如:

//src/foo/lib/parser/BUILD.bazel:

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

test_suite(
    name = "host_tests",
    # Include all tests in the package. Only use `tests = []` when every test
    # in the package belongs to this suite; if the package has both host and
    # device tests, define both `"host_tests"` and `"tests"` and list each
    # suite's tests explicitly.
    tests = [],
    visibility = ["//src/foo:__pkg__"],
)

host_rustc_test(
    name = "parser_test",
    srcs = ["src/lib.rs"],
    edition = "2024",
    target_compatible_with = HOST_OS_CONSTRAINTS,
)

//src/foo/BUILD.bazel:

test_suite(
    name = "foo_host_tests",
    tests = [
        "//src/foo/lib/parser:host_tests",
        "//src/foo/util:host_tests",
    ],
)

//src/foo/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:foo_host_tests",
  ]
}

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

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

在开发期间,您可以在定义测试目标后立即构建和运行这些目标,然后再按照上述说明导出它们。请参阅下面的直接使用 Bazel 运行测试部分。

最佳做法

如需了解有关构建 Bazel 测试和 test_suite() 目标的样式和最佳实践,请参阅测试和测试套件。如需了解 bazel_test_suite() 如何连接到 GN 测试图,以及从 GN 迁移测试时该怎么做,请参阅将 Bazel 测试挂接到 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",
    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 维度是与基础架构的接口,因此它们会保存在一个位置。

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

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

与主机测试一样,通过 "tests" test_suite() 从其软件包中公开设备测试,将这些测试分组到区域聚合 test_suite() 目标中,在同一目录中的 GN bazel_test_suite() 上的 target_tests 中列出聚合套件,并将该目标添加到 GN 中的 group("tests")(请参阅将 Bazel 测试挂接到 GN 测试图):

//src/foo/BUILD.gn:

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

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

group("tests") {
  testonly = true
  deps = [
    ":bazel_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 也会在按需构建特定测试目标时直接合并此列表。

此列表中的清单以及 tests.json 条目的 package_manifests 中的清单不是 Bazel 输出基础中的清单。在 Bazel 构建测试软件包后,构建会将其 blob 硬链接到 Ninja 构建目录,并写入指向这些 blob 的清单副本(请参阅 //build/bazel/scripts/bazel_test_packages.py)。Bazel 仅保证在请求输出的调用结束时,输出位于磁盘上,否则后续 Bazel 调用可能会删除这些使用者所需的文件。

将 Bazel 测试挂接到 GN 测试图

GN 和 Ninja 仍然是构建的入口点(尤其是在基础设施中;在某些本地工作流中,例如使用 fx test 运行单个 Bazel 测试,构建会直接调用 Bazel),因此任何 Bazel 测试目标都必须对 GN 可见,以便构建和 tests.json 包含它。每个 Bazel 测试都必须可从构建器已到达的 GN 目标访问:

fx_test() / host_xxx_test()          (package BUILD.bazel)
  <- package test_suite()            (package BUILD.bazel: "tests" / "host_tests")
  <- aggregation test_suite()        (area BUILD.bazel, e.g. "foo_tests")
  <- bazel_test_suite()              (area BUILD.gn)
  <- group("tests")                  (area BUILD.gn)
  <- area groups such as //src:tests, then builder groups in //bundles/buildbot

没有预提交检查会验证 Bazel 测试是否具有此路径。如果此路径中缺少任何链接,fx gen 和 fx build 会成功,而不会发出任何警告,并且测试不会在基础架构中静默构建或运行。添加测试后,请在 build 中添加其 GN 测试组(例如,使用 fx add-test //src/foo:tests,或将 --with-test //src/foo:tests 或 --with //bundles/buildbot/... 传递给 fx set 并运行 fx gen),然后检查测试的标签(以 @ 开头)是否显示在 out/default/tests.json 中。请在测试应运行的 build 配置中执行此操作,因为 Bazel 还会静默跳过与当前配置不兼容的测试。

构建器还可以通过 //bundles/buildbot 中的 product_bundle_test_group() 包含设备测试,该测试会针对特定产品软件包(而非构建器的默认软件包)运行其到达的测试。这是一种额外的机制;通过这种方式访问的测试仍需要 bazel_test_suite()。

测试和测试套件定义了用于构建 Bazel 测试和 test_suite() 目标的完整样式规则。概括来讲,测试套件通过三个层连接到 GN,而 GN 仍会驱动 build:

  • 在每个包含测试的软件包中公开一个软件包 test_suite()。对于设备测试,将其命名为 "tests";对于主机测试,将其命名为 "host_tests";将各个 *_test 目标设为软件包的私有目标(默认);将套件的 visibility 设为聚合它的区域软件包(使用 :__pkg__,绝不使用 :__subpackages__)。请勿在软件包的 test_suite() 中包含子软件包中的测试或 test_suite() 目标。

  • 将软件包套件分组到区域汇总 test_suite() 中,并与同一目录的 BUILD.gn 中的一个 bazel_test_suite() 配对。尽可能减少聚合软件包的数量,不要在每个中间目录中重新创建 GN 的 tests 组模式。选择开发者添加软件包以及 OWNERS 批准软件包的级别。例如,使用 //src/connectivity/bluetooth 而不是 //src/connectivity,这样添加新的蓝牙软件包就不需要连接范围内的所有者批准。 根据聚合的范围(以 "tests" 或 "host_tests" 结尾)命名聚合 test_suite(),仅在其中列出软件包 test_suite() 目标,并使用匹配的 BUILD.gn 中 bazel_test_suite() 的完整 // 标签引用它:

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

    test_suite(
        name = "tests",
        # Include all tests in the package. Only use `tests = []` when every test
        # in the package belongs to this suite; if the package has both device and
        # host tests, define both `"tests"` and `"host_tests"` and list each
        # suite's tests explicitly.
        tests = [],
        visibility = ["//src/foo:__pkg__"],
    )
    
    # Private *_test target(s) in this package.
    ...
    

    //src/foo/BUILD.bazel:

    test_suite(
        name = "foo_tests",
        tests = [
            "//src/foo/bar:tests",
            "//src/foo/baz:tests",
        ],
    )
    

    //src/foo/BUILD.gn:

    import("//build/bazel/bazel_test_suite.gni")
    
    bazel_test_suite("bazel_tests") {
      target_tests = [ "//src/foo:foo_tests" ]
    }
    
    group("tests") {
      testonly = true
      deps = [
        ":bazel_tests",
        ...
      ]
    }
    
  • 如果存在现有区域汇总套件,请向其中添加新软件包;否则,请与区域目录的所有者就区域目录达成一致,并在其中添加汇总 test_suite() 和 bazel_test_suite()。较旧的区域套件(例如 //tools:host_tests)仍会直接列出一些单独的测试目标;请保留现有条目,并为新添加或迁移的软件包公开软件包 test_suite()。

  • 将主机测试和设备测试分开。仅将设备测试套件放在 target_tests 中,仅将主机测试套件放在 host_tests 中,并且不要在任何 Bazel test_suite() 中混合使用主机和设备测试。这样做会导致构建失败和/或静默丢弃测试。

  • 将每个聚合 test_suite() 挂钩到 GN 测试图表中一次。 从两个 bazel_test_suite() 目标访问同一个 Bazel 测试套件不会运行其测试两次(相同的 tests.json 条目会合并),但会使布局更难理解。

从 GN 迁移测试时

  • 不要仅仅为了将 Bazel 测试挂接到 GN 而保留 BUILD.gn。如果目录中的每个目标都已移至 Bazel,并且没有 GN 目标依赖于它,请删除其 BUILD.gn,从父级 BUILD.gn 中移除已删除目录的 :tests 组,并将软件包的 test_suite() 添加到区域的聚合 test_suite()。即使 BUILD.gn 仍保留在仍有 GN 依赖项的目标中(请参阅在两个 build 系统中测试库),也请通过该区域的聚合 test_suite()(而不是每个目录的 bazel_test_suite())挂接 Bazel 测试套件。在由 bazel2gn 文件同步的 BUILD.bazel 文件中,将 # @bazel2gn:skip 放在软件包 test_suite() 上,这样 bazel2gn 就不会在 BUILD.gn 中生成 group("tests") 或 group("host_tests") 目标。

  • 清理了对已删除 GN 测试目标的引用。移除 GN 测试目标后(请参阅在两个 build 系统中测试库),将其从列出该目标的 GN 测试组中删除。请勿将实际构建或打包测试的依赖项替换为 bazel_test_suite():bazel_test_suite() 仅发出 tests.json 的元数据,而不会自行构建任何内容。

  • 在 GN 中保留 E2E 测试。E2E 测试(需要设备和产品软件包的主机测试)尚无法在 Bazel 中定义 (fxbug.dev/576736818)。

在两个构建系统中测试库

如果两个构建系统中的目标都使用某个库,请在两个构建系统中都运行该库的测试。当库的用户仅使用 Bazel 时,请仅在 Bazel 中运行其测试。对于仅在 Bazel 中构建的目标(例如已迁移的主机工具、没有剩余 GN 用户的库,以及不向 GN 导出库的叶集成测试套件),其测试仅在 Bazel 中运行。

迁移到 Bazel 的库会一直在 GN 中定义(通常由 bazel2gn 生成),直到没有 GN 目标依赖于它。在此期间,库的 GN 和 Bazel build 可能会存在只有测试才能发现的差异。编译器标志、build 变体(例如排错程序)和仅限 GN 的 bazel2gn 替换项在两个 build 系统之间都存在差异。在转换期间,某些属性也会被丢弃,而 GN 目标是未迁移的依赖项实际链接到的目标。在两个 build 系统中运行每个测试会消耗额外的 build 时间和测试容量,但只有当库在两个 build 系统中都有用户时才会如此。

实际应用:

  • 在 Bazel 中定义测试,并让 bazel2gn 生成 GN 副本。迁移 BUILD.gn 由 bazel2gn 生成的库时,也请迁移其测试,并且不要在测试上添加 # @bazel2gn:skip。bazel2gn 会转换 fx_cc_test_binary()、rustc_test()、go_test()、host_rustc_test()、host_go_test()、fx_unittest_package()、fx_package() 目标(使用 test_components,以及它们的 fx_test_component() 和 fx_test()),以及 Rust 规则的 with_unit_tests、with_host_unit_tests 和 test_deps 属性。如上文所述,将 Bazel 测试挂接到 GN 测试图中,并在列出原始 GN 测试的 GN 测试组中列出生成的 GN 副本。

  • 设备测试软件包在 GN 中有不同的名称。GN 测试软件包及其 Bazel 副本不能共用软件包名称,因为两者都会发布到同一个 Package Repository。bazel2gn 会将 -gn 附加到 fx_unittest_package() 或测试 fx_package() 的 GN 副本的软件包名称中,而 Bazel 测试会保留原始名称,因此其测试网址在您迁移时不会发生变化。

  • 如果 bazel2gn 尚无法转换测试的打包,而您希望继续在 GN 中运行该测试,请使用 bazel2gn 转换测试二进制文件,并在 BUILD.gn 中使用 package_name = "<name>-gn" 在 ## BAZEL2GN SENTINEL 上方手动编写 GN 测试软件包。这适用于使用 fx_unittest_package(resources = [...]) 的设备测试,或使用具有 bazel2gn 不支持的属性的测试 fx_package() 的设备测试(将 # @bazel2gn:skip 放在软件包和 fuchsia_package_resource_group() 上,而不是放在测试二进制文件上)。

  • C++ 主机测试需要手动编写的 GN host_test()。bazel2gn 会将 fx_cc_test_binary() 转换为 GN cc_test_executable()(仅构建二进制文件),但尚不会转换 host_cc_test() 或 host_test()。如需在 GN 中继续运行 C++ 主机测试,请编写一个 GN host_test(),用于在 if (is_host) 内的 BUILD.gn 中运行 ## BAZEL2GN SENTINEL 上方生成的二进制文件,并使用 ($host_toolchain) 将其列在 GN 测试组中。

  • 何时使用 # @bazel2gn:skip 选择退出。默认情况下,如果 bazel2gn 可以自动转换库的测试,请继续在两个构建系统中运行这些测试。在以下情况下,可以对测试目标或测试属性添加 # @bazel2gn:skip(并附上简短注释说明原因),并且仅在 Bazel 中运行:

    • 被测目标仅在 Bazel 中构建(例如,同一 BUILD.bazel 文件中本身具有 # @bazel2gn:skip 的二进制文件或库)。
    • bazel2gn 不支持测试的规则或属性(例如 cc_test()、host_cc_test()、host_py_test()、host_shac_test()、host_test()、具有 data/test_data/test_label 的主机测试、具有 resources 的 fx_unittest_package() 或具有跨软件包 srcs/crate 或冲突 rustc_env 的 rustc_test()),并且在哨兵上方维护手写的 GN 测试目标不值得增加额外的复杂性。
    • 在两个 build 系统中运行或维护测试的成本超过了仅 GN 回归的风险。请根据以下标准来权衡取舍:
    • 目标一致性:库的 BUILD.bazel 是否在依赖项、配置或编译器标志上使用 # @bazel2gn:raw_overwrite、# @bazel2gn:path_overwrite 或 # @bazel2gn:skip,或者它是否是在 GN 中跨多个配置编译的 IDK/SDK 库?如果生成的 GN 目标是 Bazel 目标的 1:1 转换,则 Bazel 测试已经执行了相同的代码。
    • 未迁移的 GN 依赖项:库是否依赖于经常更改的仅限 GN 的目标?预提交受影响测试分析不会从 GN 目标跟踪到 Bazel 测试,因此对仅限 GN 的依赖项所做的更改不会在预提交中触发仅限 Bazel 的测试。
    • 测试权重:快速、独立的单元测试很容易镜像;缓慢或占用大量资源的集成测试(或其剩余 GN 用户正在积极迁移的库)非常适合仅在 Bazel 中运行。
  • 如果 Bazel 尚无法表达测试,请将其保留在 ## BAZEL2GN SENTINEL 上方的 GN 中,并在注释中说明原因。例如,端到端测试、需要 Ninja 生成的输入的测试、bootfs_test() 以及跨多个 C++ 标准编译的 sdk_cpp_unittest_package()。

  • 移除库中的 GN 副本。当库的最后一个 GN 用户迁移到 Bazel 时,请删除该库及其测试的 GN 定义,并从列出这些测试的 GN 测试组中移除这些 GN 测试。

运行 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 test 用户指南。

直接使用 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 定义的主机测试时,或在收集结果时,两者之间没有明确的区别。具体而言,infra 不会使用 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 集成:BUILD.gn 中的 //:bazel_host_test_suites 会附加指向 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

在本地运行覆盖率测试时,未提交的二进制文件会生成新的 build ID,这些 ID 不存在于 debuginfod 服务器上。

为了支持本地符号关联,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)。