Bazel 測試

範圍

本文說明如何定義、建構及執行僅存在於 Bazel 圖中的測試。

Fuchsia 支援符合下列規定的 Bazel 主機測試:

  • 這些測試必須只能使用 Bazel 建構,也就是說,測試不依賴先前 Ninja 叫用產生的構件。

  • 這些測試只包含二進位檔和執行階段檔案,可在「未」連線至任何 Fuchsia 裝置或模擬器的相容主機系統上執行。

Fuchsia 也支援 Bazel 裝置測試,這類測試是以 fx_test() 宣告,並在 Fuchsia 裝置或模擬器上執行。這些規則同樣只能使用 Bazel 建構,此外:

  • 除了 environments (預設為建構作業的預設測試環境)、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 主機測試一樣。您可以透過下列其中一種機制提供執行階段依附元件:

    • 做為 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 屬性。在這種情況下,檔案會新增至測試的執行檔案,並可使用執行檔案程式庫存取。

  • 使用 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 建構作業中的元件完全相同。使用 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,測試會在建構作業的預設測試環境 (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 和基礎架構建構工具

與主機測試一樣,裝置測試必須列在 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,列舉建構圖中所有 Bazel 測試套件的套件資訊清單。完整的 fx build 叫用會將這份清單合併至 all_package_manifests.list,並將其套件發布至 amber-files,而 fx test 也會在視需要建構特定測試目標時直接合併。

在兩個建構系統中測試程式庫

如果程式庫由兩個建構系統中的目標使用,請在兩個建構系統中執行測試。當程式庫的使用者僅限 Bazel 時,請只在 Bazel 中執行測試。僅在 Bazel 中建構的目標測試 (例如遷移的主機工具、沒有剩餘 GN 使用者的程式庫,以及未將程式庫匯出至 GN 的分葉整合測試套件),只會在 Bazel 中執行。

遷移至 Bazel 的程式庫會保留在 GN 中 (通常由 bazel2gn 產生),直到沒有 GN 目標依附於該程式庫為止。在此期間,程式庫的 GN 和 Bazel 建構作業可能會有差異,只有測試才能發現。編譯器旗標、建構變化版本 (例如清理工具) 和僅限 GN 的 bazel2gn 覆寫,在兩個建構系統之間都有所不同。部分屬性也會在翻譯期間遭到捨棄,而 GN 目標是未遷移的依附元件實際連結的目標。在兩個建構系統中執行每項測試,會耗費額外的建構時間和測試容量,但只有在程式庫同時有兩個建構系統的使用者時,才會發生這種情況。

實際應用:

  • 在 Bazel 中定義測試,並讓 bazel2gn 產生 GN 副本。遷移由 bazel2gn 產生 BUILD.gn 的程式庫時,請一併遷移測試,且不要在測試中加入 # @bazel2gn:skip。bazel2gn 會轉譯 rustc_test()、go_test()、host_rustc_test()、host_go_test()、fx_unittest_package(),以及 Rust 規則的 with_unit_tests、with_host_unit_tests 和 test_deps 屬性。按照上述說明,使用 bazel_test_suite() 匯出 Bazel 測試,並在列出原始 GN 測試的 GN 測試群組中,列出產生的 GN 副本。

  • 裝置測試套件在 GN 中有不同的名稱。GN 測試套件及其 Bazel 副本會發布至同一個 Package Repository,因此不能共用套件名稱。bazel2gn 會將 -gn 附加至 fx_unittest_package() 的 GN 副本套件名稱,而 Bazel 測試會保留原始名稱,因此遷移時測試網址不會變更。

  • 如果 bazel2gn 還無法轉譯測試的套件,而您想繼續在 GN 中執行測試,請使用 bazel2gn 轉譯測試二進位檔,並在 BUILD.gn 中 ## BAZEL2GN SENTINEL 的上方,使用 package_name = "<name>-gn" 手動編寫 GN 測試套件。這適用於使用 fx_unittest_package(resources = [...]) 的裝置測試 (將 # @bazel2gn:skip 放在 fx_unittest_package() 和 fuchsia_package_resource_group() 上,而非測試二進位檔),或自訂 .cml 資訊清單的明確 fx_package() 和 fx_test_component() 目標 (bazel2gn 已忽略沒有 # @bazel2gn:skip 的目標)。

  • 何時應使用 # @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 測試目標不值得額外增加複雜度。
    • 在兩個建構系統中執行或維護測試的成本,高於僅限 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 測試群組中移除 GN 測試。

執行 Fuchsia Bazel 裝置測試

匯出後,請按照 GN 測試的方式,透過套件網址或套件名稱執行 Bazel 裝置測試:

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 建構作業時:

  1. 測試查詢:bazel_tests_utils.py 使用 //build/bazel/starlark/FuchsiaHostTestInfo.cquery 執行 bazel cquery,為所有已註冊的主機測試擷取 unstripped_binary 的 execroot 路徑。

  2. 主機測試資訊清單:路徑會相對於 Ninja 建構目錄進行正規化,並寫入 ${root_build_dir}/bazel_host_tests.debug_symbols.json。

  3. GN 建構 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 建構作業中,Artifactory 會處理 debug_symbols.json,並將未去除的 ELF 二進位檔上傳至雲端儲存空間 / debuginfod 伺服器,讓 covargs 等工具解析建構 ID。

本機開發和 fx coverage

在本機執行涵蓋率時,未提交的二進位檔會產生新的建構 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 建構 ID,並將未去除的二進位檔複製或符號連結至 ${root_build_dir}/.build-id/xx/yyyyyyyy.debug。ffx coverage 和 llvm-profdata 等工具會透過 .symbol-index.json 解析這些二進位檔。

常見問題

Bazel 測試相容性

如果 test_suite() (或其任何依附元件) 中的任何測試與目前設定不相容 (例如使用 target_compatible_with),系統會在評估套件時略過該測試,而不會導致建構失敗。

在指令列使用 //... 和 :all 等模式時,也適用同樣的規則。

詳情請參閱不相容目標的處理方式。

執行 test_suite() 或使用目標模式時,開發人員必須確認相關測試回報為 PASSED (而非 SKIPPED)。