Fuchsia 測試執行者架構

Fuchsia 元件架構可讓開發人員使用各種語言和執行階段建立元件。Fuchsia 本身的程式碼會使用多種程式設計語言,用於元件,包括 C/C++、Rust、Dart 和 Go。

測試執行器架構會使用元件架構執行器,做為各種測試執行階段與常見 Fuchsia 通訊協定之間的整合層,用於啟動測試並接收測試結果。這項設計可讓開發人員使用所選語言和測試架構,並在各種系統上建構及測試 Fuchsia,並鎖定不同的硬體。

測試管理工具

test_manager 元件負責在 Fuchsia 裝置上執行測試。測試管理員會公開 fuchsia.test.manager.RunBuilder 通訊協定,可用於啟動測試套件。

每個測試套件都會以測試管理員的子項啟動。測試套件是由測試管理員提供的功能,可讓測試套件在執行工作時,維持與系統其他部分的隔離。舉例來說,密封測試可記錄訊息,但無法在沙箱之外與實際系統資源互動。測試管理員只會使用測試領域的一個能力,也就是測試套件公開的控制器通訊協定。這麼做是為了確保密封性 (測試結果不會受到所屬沙箱以外的任何內容影響) 和隔離性 (測試不會影響彼此或其他系統)。

測試管理器控制器本身會提供給系統中的其他元件,以便將測試執行作業與各種開發人員工具整合。接著,您可以使用 fx testffx 等工具啟動測試。

測試套件通訊協定

測試管理員會使用測試套件通訊協定 fuchsia.test.Suite 來控制測試,例如叫用測試案例並收集結果。

測試作者通常不需要實作此通訊協定。而是仰賴測試執行程式代為執行這項操作。舉例來說,您可以使用 GoogleTest 架構,以 C++ 編寫測試,然後在元件資訊清單中使用 gtest_runner 與測試執行程式架構整合。

測試執行程式

包含語言和執行階段的架構

測試執行器是測試執行器架構與開發人員用於編寫測試的常用語言和架構之間的可重複使用轉接器。他們會代表測試作者實作 fuchsia.test.Suite 通訊協定,讓開發人員能夠針對所選語言和架構編寫慣用測試。

建構規則可產生簡單單元測試的元件資訊清單。針對 v2 測試產生的元件資訊清單,會根據建構定義納入適當的測試執行元件。舉例來說,如果測試可執行檔依賴 GoogleTest 程式庫,則會在產生的資訊清單中加入 GoogleTest 執行工具

測試執行器的庫存

目前可供一般使用的測試執行器如下:

GoogleTest 執行元件

使用 GoogleTest 架構,以 C/C++ 編寫的測試執行程式。請將此值用於所有使用 GoogleTest 編寫的測試。

支援常見的 GoogleTest 功能,例如停用測試、只執行指定的測試、多次執行相同的測試等。從測試中擷取標準輸出內容、標準錯誤和記錄。

如要使用這個執行元件,請在元件資訊清單中新增下列內容:

{
    include: [ "//src/sys/test_runners/gtest/default.shard.cml" ]
}

根據預設,GoogleTest 測試案例會依序執行 (一次執行一個測試案例)。

GoogleTest (GUnit) 執行元件

使用 GUnit 架構,以 C/C++ 編寫的測試執行程式。請針對使用 GoogleTest 的 gUnit 風格編寫的所有測試使用此選項。

支援常見的 GoogleTest 功能,例如停用測試、只執行指定的測試、多次執行相同的測試等。從測試中擷取標準輸出內容、標準錯誤和記錄。

如要使用這個執行元件,請在元件資訊清單中新增下列內容:

{
    include: [ "sys/testing/gunit_runner.shard.cml" ]
}

根據預設,測試案例會依序執行 (一次執行一個測試案例)。

Rust 執行元件

以 Rust 程式設計語言編寫,並遵循 Rust 測試慣用法的測試執行元件。請針對所有慣用 Rust 測試 (也就是使用設定屬性 [cfg(test)] 的模組進行測試) 使用此選項。

支援常見的 Rust 測試功能,例如停用測試、只執行指定的測試、多次執行相同的測試等。從測試中擷取標準輸出內容、標準錯誤和記錄。

如要使用這個執行元件,請在元件資訊清單中新增下列內容:

{
    include: [ "//src/sys/test_runners/rust/default.shard.cml" ]
}

根據預設,Rust 會並行執行測試案例,一次最多 10 個。

Go 測試執行元件

這個執行元件會執行以 Go 程式設計語言編寫的測試,並遵循 Go 測試慣用法。請針對使用 import "testing" 以 Go 編寫的所有測試使用此選項。

支援常見的 Go 測試功能,例如停用測試、只執行指定的測試、多次執行相同的測試等。從測試中擷取標準輸出內容、標準錯誤和記錄。

如要使用這個執行元件,請在元件資訊清單中新增下列內容:

{
    include: [ "//src/sys/test_runners/gotests/default.shard.cml" ]
}

根據預設,Go 測試案例會並行執行,每次最多 10 個。

ELF 測試執行元件

這是最簡單的測試執行元件,它會等待程式終止,然後回報測試是否通過:如果程式傳回零,則表示測試通過;如果傳回非零值,則表示測試失敗。

如果測試是以 ELF 程式 (例如以 C/C++ 編寫的可執行檔) 實作,但未使用現有執行工具支援的一般測試架構,且您不想實作專屬的測試執行元件,請使用這個測試執行工具。

如要使用這個執行元件,請在元件資訊清單中新增下列內容:

{
    include: [ "sys/testing/elf_test_runner.shard.cml" ]
}

如果您樹狀結構內單元測試 GN 範本,但尚未使用內含專屬測試執行元件的測試架構,請將下列內容加入建構依附元件:

fuchsia_unittest_package("my-test-packkage") {
    // ...
    deps = [
        // ...
        "//src/sys/testing/elftest",
    ]
}

控制測試案例的並行執行

使用 fx test 啟動測試時,可以依序執行每個測試案例,也可以並行執行多個測試案例,但最多只能執行指定數量。預設的並行處理行為是由測試執行元件決定。如要手動控管要同時執行的測試案例數量,請使用測試規格:

fuchsia_test_package("my-test-pkg") {
  test_components = [ ":my_test_component" ]
  test_specs = {
    # control the parallelism
    parallel = 10
  }
}

多次執行測試

如要多次執行測試,請使用:

 fx test --count=<n> <test_url>

如果某個迴圈逾時,系統就不會再執行其他迴圈。

傳遞引數

您可以使用 fx test 傳遞自訂測試引數:

fx test <test_url> -- <custom_args>

個別測試執行程式對這些自訂標記設有限制:

GoogleTest 執行元件

請注意下列已知的行為變更:

--gtest_break_on_failure - 改用以下命令:

fx test --break-on-failure <test_url>

下列標記受到限制,如果有任何標記傳遞,fuchsia.test.Suite 會提供等效功能取代這些標記,因此測試會失敗。

  • --gtest_filter - 改用以下命令:
 fx test --test-filter=<glob_pattern> <test_url>

--test-filter 可多次指定。系統會執行符合任何指定 glob 模式的測試。

  • --gtest_also_run_disabled_tests - 改用以下命令:
 fx test --also-run-disabled-tests <test_url>
  • --gtest_repeat:請參閱「多次執行測試」。
  • --gtest_output - 系統不支援發出 gtest JSON 輸出內容。
  • --gtest_list_tests - 不支援列出測試案例。

GoogleTest (GUnit) 執行元件

請注意下列已知的行為變更:

--gunit_break_on_failure - 改用以下命令:

fx test --break-on-failure <test_url>

下列標記受到限制,如果有任何標記傳遞,fuchsia.test.Suite 會提供等效功能取代這些標記,因此測試會失敗。

  • --gunit_filter - 改用以下命令:
 fx test --test-filter=<glob_pattern> <test_url>

--test-filter 可多次指定。系統會執行符合任何指定 glob 模式的測試。

  • --gunit_also_run_disabled_tests - 改用:
 fx test --also-run-disabled-tests <test_url>
  • --gunit_repeat - 請參閱「多次執行測試」一節。
  • --gunit_output - 系統不支援發出 gtest json/xml 輸出內容。
  • --gunit_list_tests - 不支援列出測試案例。

Rust 執行元件

下列標記受到限制,如果有任何標記傳遞,fuchsia.test.Suite 會提供等效功能取代這些標記,因此測試會失敗。

  • <test_name_matcher> - 請改用:
 fx test --test-filter=<glob_pattern> <test_url>

--test-filter 可多次指定。系統會執行符合任何指定 glob 模式的測試。

  • --nocapture - 預設會輸出輸出內容。
  • --list - 不支援列出測試案例。

Go 測試執行元件

請注意下列已知的行為變更:

-test.failfast:由於每個測試案例會在不同的程序中執行,這個標記只會影響子測試。

下列標記受到限制,如果有任何標記傳遞,則測試會失敗,因為 fuchsia.test.Suite 提供可取代這些標記的等效功能

  • -test.run - 請改用:
 fx test --test-filter=<glob_pattern> <test_url>

--test-filter 可多次指定。系統會執行符合任何指定 glob 模式的測試。

不受執行階段影響的執行階段測試架構

Fuchsia 的目標是提供包容性,舉例來說,開發人員可以使用所選的語言和執行階段,建立元件 (及其測試)。設計上,Test Runner Framework 本身不區分語言,個別的測試執行程式專門用於特定程式語言或測試執行階段,因此可納入所有語言。任何人都可以建立及使用新的測試執行工具。

建立新的測試執行程式相對容易,而且可以將程式碼分享給不同的執行程式。舉例來說,GoogleTest 執行元件和 Rust 執行元件會共用與啟動 ELF 二進位檔相關的程式碼,但在將指令列引數傳遞至測試,以及剖析測試結果的程式碼方面有所不同。

暫時儲存空間

如要在測試中使用暫時性儲存空間,請在元件資訊清單中新增以下內容:

{
    include: [ "//src/sys/test_runners/tmp_storage.shard.cml" ]
}

在執行階段,測試將具有 /tmp 的讀取/寫入存取權。測試開始時,這個目錄的內容會是空白,並且會在測試結束後刪除。

如果測試未指定自訂資訊清單,而是依賴建構系統產生元件資訊清單,則可以新增下列依附元件:

fuchsia_unittest_package("foo-tests") {
  deps = [
    ":foo_test",
    "//src/sys/test_runners:tmp_storage",
  ]
}

匯出自訂檔案

如要從測試中匯出自訂檔案,請使用 custom_artifacts 儲存空間能力。系統會在測試結束時複製 custom_artifacts 的內容。

如要在測試中使用 custom_artifacts,請在元件資訊清單中新增下列內容:

{
    use: [
        {
            storage: "custom_artifacts",
            rights: [ "rw*" ],
            path: "/custom_artifacts",
        },
    ],
}

在執行階段,測試將具有 /custom_artifacts 的讀取/寫入存取權。測試開始時,這個目錄的內容會是空白,並且會在測試結束後刪除。

請參閱自訂構件測試範例。如要執行此功能,請將 //examples/tests/rust:tests 新增至建構,然後執行以下指令:

fx test --ffx-output-directory <output-dir> custom_artifact_user

測試結束後,<output-dir> 會包含測試產生的 artifact.txt 檔案。

密封

在軟體測試的情況下,密封性是指將測試或測試套件與外部因素和依附元件隔離,確保無論周遭環境如何變化,都能產生一致且可靠的結果。密封測試是自給自足的,不會依賴可能會發生意外變化的外部系統或資料,導致不穩定或非確定性的測試結果。

密封性並不代表可從穩定的平台或路由功能/API 獲得保護。如果 API 途徑對於系統中的特定元件不穩定,則會對測試和依附元件造成不穩定,並且會協助擷取因系統 API 途徑的任何直接或間接變更而導致的回歸。

能夠編寫完整且可證明為密封的測試,是 Fuchsia 的測試超能力。測試密封性有兩種方式:

  • 功能:測試不會使用提供任何來自測試根目錄父項的功能。這些測試不會存取任何可能影響大型系統的系統功能。由於密封測試具有這項特性,因此可以並行執行,且不會因交談或共用狀態而中斷,進而改善測試的穩定性和效能。

  • 套件:測試不會解析測試套件以外的任何元件。密封封裝的測試與平台套件沒有任何隱含合約。這樣一來,您就能在不影響系統套件且不依附不相容的套件依附元件的情況下,更新這些套件。

密封性不適用於系統中所有元件都可使用的平台 API/功能。例如

  • clock
  • 核心提供的 ID,例如 koid。
  • 為所有元件提供架構功能,允許用戶端變更元件管理員狀態,雖然這不是嚴格的密封,但元件管理員有責任確保隔離

測試時應謹慎使用這些 API/功能。

除非另有明確說明,否則測試預設為密封式。

測試的密封功能

有些功能可供所有測試使用,且不會違反測試密封性:

通訊協定 說明
fuchsia.boot.WriteOnlyLog 寫入核心記錄
fuchsia.logger.LogSink 寫入 syslog
fuchsia.process.Launcher 從測試套件啟動子程序
fuchsia.diagnostics.ArchiveAccessor 讀取測試中元件產生的診斷輸出內容

這些功能經過精心策劃,因此可確保測試不會影響測試領域或其他測試以外的系統元件行為,因此可保留密封性。

如要使用這些功能,請在測試的資訊清單檔案中新增使用宣告:

// my_test.cml
{
    use: [
        ...
        {
            protocol: [
              "fuchsia.logger.LogSink"
            ],
        },
    ],
}

測試也提供一些預設的儲存功能,這些功能會在測試執行完畢後銷毀。

儲存空間功能 說明 路徑
data 隔離的資料儲存空間目錄 /data
cache 隔離的快取儲存空間目錄 /cache
tmp 記憶體內隔離的臨時儲存目錄 /tmp

在測試的資訊清單檔案中新增使用宣告,以便使用這些功能。

// my_test.cml
{
    use: [
        ...
        {
            storage: "data",
            path: "/data",
        },
    ],
}

此架構也會為所有元件提供一些功能,並可視需要供測試元件使用。

密封元件解析

密封測試元件會在使用密封元件解析器的領域中啟動。這個解析器不允許解析測試套件以外的網址。這項做法有助於確保密封性,因為我們不希望系統或相關聯的套件伺服器中任意元件的可用性,會影響測試結果。

如果嘗試解析不在測試套件中的元件,系統會在 syslog 中顯示 PackageNotFound 錯誤和以下訊息:

failed to resolve component fuchsia-pkg://fuchsia.com/[package_name]#meta/[component_name]: package [package_name] is not in the set of allowed packages...

您可以將測試所需的任何元件納入測試套件,藉此避免發生這項錯誤。如需相關範例,請參閱這個 CL,或使用子套件

# BUILD.gn
import("//build/components.gni")


fuchsia_test_package("simple_test") {
  test_components = [ ":simple_test_component" ]
  subpackages = [ "//path/to/subpackage:subpackage" ]
}
 // test.cml
 {
...
    children: [
        {
            name: "child",
            url: "subpackage#meta/subpackaged_component.cm",
        },
    ],
...
}

請參閱這個 CL,瞭解如何使用子套件。

// my_component_test.cml

{
...

    facets: {
        "fuchsia.test": {
            "deprecated-allowed-packages": [ "non_hermetic_package" ],
        },
    },
...
}

非密封測試

這些測試可以存取測試領域以外的部分預先定義功能。由非密封測試從測試領域外存取的功能稱為系統功能

如要使用系統能力,測試必須明確標示自己要在非密封領域中執行,如下所示。

# BUILD.gn (in-tree build rule)

fuchsia_test_component("my_test_component") {
  component_name = "my_test"
  manifest = "meta/my_test.cml"
  deps = [ ":my_test_bin" ]

  # This runs the test in "system-tests" non-hermetic realm.
  test_type = "system"
}

與建構規則整合後,即可執行測試,如下所示:

fx test <my_test>

或非樹狀結構外結構開發人員

ffx test run --realm <realm_moniker> <test_url>

在上述範例中,realm_moniker 應替換為 /core/testing/system-tests

test_type 可能的值:

說明
chromium Chromium 測試領域
ctf CTF 測試領域
device 裝置測試
drm DRM 測試
starnix Starnix 測試
system_validation 系統驗證測試
system 舊版非密封領域,可存取部分系統功能。
test_arch 測試架構測試
vfs-compliance VFS 法規遵循測試
vulkan Vulkan 測試

瞭解如何建立自己的測試領域。

非密封的舊版測試領域

這些是以服務形式提供的 Test Manager 推出前建立的舊版測試領域。我們正在移植這些領域。如果您的測試依賴其中一個領域,則應明確標示自己要在舊版領域中執行,如下所示。

// my_component_test.cml

{
    include: [
        // Select the appropriate test runner shard here:
        // rust, gtest, go, etc.
        "//src/sys/test_runners/rust/default.shard.cml",

        // This includes the facet which marks the test type as 'starnix'.
        "//src/devices/testing/starnix_test.shard.cml",
    ],
    program: {
        binary: "bin/my_component_test",
    },
    
    use: [
        {
            protocol: [ "fuchsia.vulkan.loader.Loader" ],
        },
    ],
}

該區塊在資訊清單檔案中包含下列切面:

// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
{
    include: [
        "//src/starnix/tests/starnix_test_common.shard.cml",
        "//src/storage/fxfs/test-fxfs/meta/test-fxfs.shard.cml",
    ],
    offer: [
        {
            storage: "data",
            from: "self",
            to: [ "#container" ],
        },
        {
            protocol: "fuchsia.fxfs.CryptManagement",
            from: "#test-fxfs",
            to: [ "#container" ],
        },
    ],
    expose: [
        {
            protocol: "fuchsia.test.Suite",
            from: "self",
        },
    ],
}

fuchsia.test.type 可能的值:

說明
hermetic 密封領域
chromium-system Chromium 系統測試領域
google Google 測試領域

受限制的記錄檔

根據預設,如果測試記錄的訊息嚴重性為 ERROR 以上,則會失敗。詳情請參閱這份指南

成效

撰寫啟動程序的測試執行元件時,執行元件需要提供程式庫載入器實作。

測試執行程式通常會在個別程序中啟動個別測試案例,以便在測試案例之間達到更高程度的隔離。不過,這可能會造成重大的效能成本。為減輕這項負擔,上述列出的測試執行程式會使用快取載入器服務,藉此減少每個啟動程序的額外額外負擔。

測試角色

測試領域中的元件可能在測試中扮演不同角色,如下所示:

  • 測試根:位於測試元件樹狀結構頂端的元件。測試的網址會識別這個元件,而測試管理員會叫用這個元件公開的 fuchsia.test.Suite,以便執行測試。
  • 測試驅動程式:實際執行測試並實作 (直接或透過測試執行器) fuchsia.test.Suite 通訊協定。請注意,測試驅動程式庫和測試根目錄可能相同,但不一定相同:測試驅動程式庫可能是測試根目錄的子元件,例如重新公開其 fuchsia.test.Suite
  • 功能提供者:提供測試會以某種方式運作的能力的元件。元件可能會提供測試能力的「假」實作,或是與實際使用情況相等的「真」實作。
  • 測試中的元件:執行某些待測行為的元件。這可能與實際工作環境中的元件相同,或是專門用於模擬實際工作環境行為的測試元件。

疑難排解

本節將說明您在使用 Test Runner Framework 開發測試元件時可能會遇到的常見問題。如果某個測試元件無法執行,您可能會在 fx test 中看到類似下列的錯誤訊息:

Test suite encountered error trying to run tests: getting test cases
Caused by:
    The test protocol was closed. This may mean `fuchsia.test.Suite` was not configured correctly.

如要解決這個問題,請嘗試下列選項:

測試使用錯誤的測試執行元件

如果您在測試列舉期間遇到這項錯誤,可能是因為您使用錯誤的測試執行元件。

舉例來說,您的 Rust 測試檔案可能會在未使用 Rust 測試架構的情況下執行測試 (也就是說,它是具有自身主函式的簡易 Rust 二進位檔)。在這種情況下,請變更測試資訊清單檔案,以便使用 elf_test_runner

進一步瞭解內建的測試執行程式

測試無法將 fuchsia.test.Suite 公開給測試管理員

發生這種情況的原因是,測試根目錄無法從測試根目錄公開 fuchsia.test.Suite。簡單的修正方式是新增 expose 宣告:

// test_root.cml
expose: [
    ...
    {
        protocol: "fuchsia.test.Suite",
        from: "self",  // If a child component is the test driver, put `from: "#driver"`
    },
],

測試驅動程式庫無法將 fuchsia.test.Suite 公開至根目錄

如果 fuchsia.test.Suite 通訊協定未正確公開,測試可能會失敗,並顯示類似下列的錯誤:

ERROR: Failed to route protocol `/svc/fuchsia.test.Suite` from component
`/test_manager/...`: An `expose from #driver` declaration was found at `/test_manager/...`
for `/svc/fuchsia.test.Suite`, but no matching `expose` declaration was found in the child

如果測試驅動程式庫和測試根目錄是不同的元件,則測試驅動程式庫也必須將 fuchsia.test.Suite 公開給其父項,也就是測試根目錄。

如要解決這個問題,請確認測試驅動程式元件資訊清單包含下列 expose 宣告:

// test_driver.cml
expose: [
    ...
    {
        protocol: "fuchsia.test.Suite",
        from: "self",
    },
],

測試驅動程式庫不會使用測試執行元件

測試驅動程式必須使用適當的測試執行程式,以對應測試所使用的語言和測試架構。舉例來說,Rust 測試的驅動程式庫需要下列宣告:

// test_driver.cml
include: [ "//src/sys/test_runners/rust/default.shard.cml" ]

此外,如果測試驅動程式庫是測試根的子項,您必須將其提供給驅動程式庫:

// test_root.cml
offer: [
    {
        runner: "rust_test_runner",
        to: [ "#driver" ],
    },
],

其他資訊