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 也會在視需要建構特定測試目標時,直接合併這個清單。

執行 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 主機測試,必須按照上述「fx test 匯出 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 中顯示,否則基礎架構不會持續測試。

基礎架構會使用 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。
  • 如果測試使用包裝函式指令碼啟動器 (例如 rust_test_parser 中的 host_rustc_test()),巨集必須透過 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 Build API 整合://:bazel_host_test_suites 會在 BUILD.gn 中附加指向 debug_symbol_manifests 的中繼資料,bazel_host_tests.debug_symbols.json 會使用這些中繼資料。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)。