測試百科全書

測試執行環境的詳盡規格。

背景

Bazel BUILD 語言包含多種規則,可用於定義多種語言的自動測試程式。

測試是使用 bazel test 執行。

使用者也可以直接執行測試二進位檔。雖然允許這麼做,但我們不建議,因為這類叫用方式不會遵守下述規定。

測試應為密封狀態,也就是說,測試只能存取已宣告依附元件的資源。如果測試並未適當密封,就無法提供可重現的歷來結果。這可能會對找出罪魁禍首 (判斷哪些變更導致測試失敗)、發布工程稽核能力,以及測試的資源隔離 (自動化測試架構不應因為某些測試會與伺服器通訊,就對伺服器發動 DDoS 攻擊) 造成重大問題。

目標

本頁面的目標是正式建立 Bazel 測試的執行階段環境和預期行為。也會對測試執行器和建構系統設下規定。

測試環境規格可協助測試作者避免依賴未指定的行為,因此測試基礎架構可更自由地進行實作變更。目前有許多測試並非完全密封、具決定性且可重入,但仍可通過測試,而這項規格可填補部分漏洞。

本頁面旨在提供規範和權威資訊。如果這項規格與測試執行程式的實作行為不一致,則以規格為準。

建議規格

「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」和「OPTIONAL」等關鍵字應按照 IETF RFC 2119 的說明解讀。

測試目的

Bazel 測試的目的是確認簽入存放區的來源檔案是否具有某些屬性。(在本頁面中,「來源檔案」包括測試資料、黃金輸出內容,以及版本控管下的任何其他內容)。一位使用者編寫測試,用來判斷預期會維持不變的項目。其他使用者稍後會執行測試,確認不變量是否已遭破壞。如果測試取決於來源檔案以外的任何變數 (非密封),測試的價值就會降低,因為測試停止通過時,後續使用者無法確定變更是否為錯誤。

因此,測試結果只能取決於:

  • 測試聲明依附的來源檔案
  • 測試已宣告依附的建構系統產品
  • 測試執行器保證行為保持不變的資源

目前不會強制執行這類行為。不過,測試執行器保留日後新增這類強制執行的權利。

建構系統的角色

測試規則與二進位規則類似,兩者都必須產生可執行的程式。對於某些語言,這是將語言專屬的測試架構與測試程式碼結合的虛設常式程式。測試規則也必須產生其他輸出內容。除了主要測試可執行檔,測試執行器還需要 runfiles 的資訊清單、應在執行階段提供給測試的輸入檔案,以及測試的類型、大小和標記等資訊。

建構系統可能會使用執行檔傳送程式碼和資料。(這項功能可用於最佳化作業,例如透過動態連結在測試之間共用檔案,藉此縮小每個測試二進位檔的大小)。建構系統應確保產生的可執行檔透過測試執行器提供的執行檔映像檔載入這些檔案,而不是原始碼或輸出樹狀結構中絕對位置的硬式編碼參照。

測試執行工具的角色

從測試執行工具的角度來看,每項測試都是可透過 execve() 叫用的程式。可能還有其他執行測試的方法,例如 IDE 可能允許在程序中執行 Java 測試。不過,以獨立程序執行測試的結果必須視為權威。如果測試程序執行完畢,並以結束代碼零正常終止,則測試通過。其他任何結果都視為測試失敗。特別是,將任何字串 PASS 或 FAIL 寫入 stdout 對測試執行器沒有任何意義。

如果測試執行時間過長、超出某些資源限制,或測試執行器偵測到禁止行為,可能會選擇終止測試,並將執行作業視為失敗。跑步者不得在將信號傳送至測試程序或其任何子項後,將測試回報為通過。

整個測試目標 (而非個別方法或測試) 的執行時間有限,測試的時間限制取決於 timeout 屬性,如下表所示:

逾時 時間限制 (秒)
short 60
中度 300
long 900
eternal 3600

如果測試未明確指定逾時時間,系統會根據測試的 size 隱含逾時時間,如下所示:

大小 隱含逾時標籤
small short
中 中度
large long
巨大 eternal

如果「大型」測試沒有明確的逾時設定,系統會分配 900 秒的執行時間。如果「中」測試的逾時時間為「短」,則會分配 60 秒。

與 timeout 不同,size 還會判斷在本機執行測試時,其他資源 (例如 RAM) 的假設尖峰用量,詳情請參閱「常見定義」。

size 和 timeout 標籤的所有組合都合法,因此「巨大」測試可宣告為「短」逾時。想必會很快做出一些非常可怕的事情。

無論逾時與否,測試都可能會傳回任意快速的結果。如果逾時時間過長,測試不會受到處罰,但可能會發出警告:一般來說,您應盡可能縮短逾時時間,以免發生任何不穩定情況。

手動在已知速度緩慢的條件下執行時,可以使用 --test_timeout bazel 旗標覆寫測試逾時。--test_timeout 值以秒為單位。舉例來說,--test_timeout=120 會將測試逾時時間設為兩分鐘。

此外,測試逾時的建議下限如下:

逾時 時間下限 (秒)
short 0
中度 30
long 300
eternal 900

舉例來說,如果「中等」測試在 5.5 秒內完成,請考慮設定 timeout = "short" 或 size = "small"。使用 bazel --test_verbose_timeout_warnings 指令列選項,即可顯示指定大小過大的測試。

測試大小和逾時時間是在 BUILD 檔案中指定,詳情請參閱這篇文章。如果未指定,測試大小預設為「中」。

如果測試的主要程序結束,但部分子程序仍在執行,測試執行器應將執行視為完成,並根據主要程序觀察到的結束代碼,將其計為成功或失敗。測試執行工具可能會終止任何多餘的程序。測試不應以這種方式洩漏程序。

測試分片

測試可透過測試分片平行化。請參閱「--test_sharding_strategy」和「shard_count」,瞭解如何啟用測試資料分割。啟用資料分割後,系統會針對每個分片啟動一次測試執行者。環境變數 TEST_TOTAL_SHARDS 是分片數量,而 TEST_SHARD_INDEX 是分片索引,從 0 開始。執行器會使用這項資訊選取要執行的測試,例如使用輪詢策略。並非所有測試執行器都支援分片。如果執行器支援分片,就必須建立或更新 TEST_SHARD_STATUS_FILE 指定檔案的上次修改日期,否則 Bazel 會在測試分片時失敗。

初始條件

執行測試時,測試執行器必須建立特定初始條件。

測試執行工具必須使用 argv[0] 中的測試可執行檔路徑,叫用每項測試。這個路徑必須是相對路徑,且位於測試的目前目錄下方 (位於 runfiles 樹狀結構中,請參閱下文)。除非使用者明確要求,否則測試執行器不應將任何其他引數傳遞至測試。

初始環境區塊應包含下列內容:

變數 值 狀態
HOME $TEST_TMPDIR 的值 建議
LANG 未設定 必填
LANGUAGE 未設定 必填
LC_ALL 未設定 必填
LC_COLLATE 未設定 必填
LC_CTYPE unset 或 C.UTF-8 必填
LC_MESSAGES 未設定 必填
LC_MONETARY 未設定 必填
LC_NUMERIC 未設定 必填
LC_TIME 未設定 必填
LD_LIBRARY_PATH 以半形冒號分隔的目錄清單,內含共用程式庫 選用
JAVA_RUNFILES $TEST_SRCDIR 的值 已淘汰
LOGNAME $USER 的值 必填
PATH /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:. 建議
PWD $TEST_SRCDIR/workspace-name 建議
SHLVL 2 建議
TEST_INFRASTRUCTURE_FAILURE_FILE 可寫入目錄中私有檔案的絕對路徑 (這個檔案只能用於回報源自測試基礎架構的失敗,不能做為回報測試不穩定失敗的一般機制。在此情況下,測試基礎架構是指並非測試專用,但故障時可能導致測試失敗的系統或程式庫。第一行是導致失敗的測試基礎架構元件名稱,第二行則是失敗的易讀說明。系統會忽略其他行。) 選用
TEST_LOGSPLITTER_OUTPUT_FILE 可寫入目錄中私有檔案的絕對路徑 (用於寫入 Logsplitter protobuffer 記錄) 選用
TEST_PREMATURE_EXIT_FILE 可寫入目錄中私密檔案的絕對路徑 (用於擷取對 exit() 的呼叫) 選用
TEST_RANDOM_SEED 如果使用 --runs_per_test 選項,系統會為每個個別測試執行作業將 TEST_RANDOM_SEED 設為 run number (從 1 開始)。 選用
TEST_RUN_NUMBER 如果使用 --runs_per_test 選項,系統會為每個個別測試執行作業將 TEST_RUN_NUMBER 設為 run number (從 1 開始)。 選用
TEST_TARGET 要測試的目標名稱 選用
TEST_SIZE 測試 size 選用
TEST_TIMEOUT 測試 timeout (以秒為單位) 選用
TEST_SHARD_INDEX 分片索引 (如果使用 sharding) 選用
TEST_SHARD_STATUS_FILE 要觸控的檔案路徑,表示支援 sharding 選用
TEST_SRCDIR 執行檔樹狀結構底部的絕對路徑 必填
TEST_TOTAL_SHARDS 總計 shard count, 如果使用 sharding 選用
TEST_TMPDIR 可寫入的私有目錄絕對路徑 必填
TEST_WORKSPACE 本機存放區的工作區名稱 選用
TEST_UNDECLARED_OUTPUTS_DIR 可寫入的私人目錄的絕對路徑 (用於寫入未宣告的測試輸出內容)。寫入 TEST_UNDECLARED_OUTPUTS_DIR 目錄的所有檔案都會壓縮,並新增至 bazel-testlogs 下的 outputs.zip 檔案。 選用
TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR 可寫入的私有目錄的絕對路徑 (用於寫入未宣告的測試輸出註解 .part 和 .pb 檔案)。 選用
TEST_WARNINGS_OUTPUT_FILE 可寫入目錄中私有檔案的絕對路徑 (用於寫入測試目標警告) 選用
TESTBRIDGE_TEST_ONLY --test_filter 的值 (如已指定) 選用
TZ UTC 必填
USER getpwuid(getuid())->pw_name 的值 必填
XML_OUTPUT_FILE 測試動作應將測試結果 XML 輸出檔案寫入的位置。 否則,Bazel 會產生預設 XML 輸出檔案,將測試記錄包裝成測試動作的一部分。XML 架構是以 JUnit 測試結果架構為基礎。 選用
BAZEL_TEST 表示測試可執行檔是由 bazel test 驅動 必填

環境可能包含其他項目。測試不應依賴上述未列出的任何環境變數是否存在或值為何。

初始工作目錄應為 $TEST_SRCDIR/$TEST_WORKSPACE。

目前的程序 ID、程序群組 ID、工作階段 ID 和上層程序 ID 皆未指定。該程序可能是程序群組領導者或工作階段領導者,也可能不是。程序可能會有或沒有控制終端機。程序可能會有零個或多個執行中或未收割的子程序。測試程式碼取得控制權時,程序不應有多個執行緒。

檔案描述元 0 (stdin) 應開啟以供讀取,但附加的內容未指定。測試不得從中讀取資料。檔案描述元 1 (stdout) 和 2 (stderr) 應開啟以供寫入,但附加的內容未指定。可以是終端機、管道、一般檔案,或任何可寫入字元的項目。他們可能會共用開放式檔案表格中的項目 (也就是說,他們無法獨立搜尋)。測試不應繼承任何其他開啟的檔案描述元。

初始 umask 應為 022 或 027。

不得有待處理的鬧鐘或間歇計時器。

遭封鎖信號的初始遮罩應為空白。所有信號都應設為預設動作。

初始資源限制 (軟性與硬性) 應設定如下:

資源 限制
RLIMIT_AS 無限制
RLIMIT_CORE 未指定
RLIMIT_CPU 無限制
RLIMIT_DATA 無限制
RLIMIT_FSIZE 無限制
RLIMIT_LOCKS 無限制
RLIMIT_MEMLOCK 無限制
RLIMIT_MSGQUEUE 未指定
RLIMIT_NICE 未指定
RLIMIT_NOFILE 至少 1024
RLIMIT_NPROC 未指定
RLIMIT_RSS 無限制
RLIMIT_RTPRIO 未指定
RLIMIT_SIGPENDING 未指定
RLIMIT_STACK 無限制,或 2044KB <= rlim <= 8192KB

初始程序時間 (由 times() 傳回) 和資源用量 (由 getrusage() 傳回) 並不明確。

初始排程政策和優先順序未指定。

主機系統的角色

除了測試執行器直接控管的使用者環境方面,執行測試的作業系統也必須滿足特定屬性,測試執行作業才有效。

檔案系統

測試觀察到的根目錄可能不是實際的根目錄。

/proc。

所有建構工具都應位於本機安裝使用的 /usr 下的絕對路徑。

開頭為 /home 的路徑可能無法使用。測試不應存取任何這類路徑。

/tmp 應可寫入,但測試應避免使用這些路徑。

測試不得假設任何常數路徑可供專屬使用。

測試不得假設任何已掛接的檔案系統已啟用 atime。

使用者和群組

使用者必須存在於根目錄、nobody 和 unittest。群組根目錄、nobody 和 eng 必須存在。

測試必須以非超級使用者身分執行。實際和有效使用者 ID 必須相等,群組 ID 也是如此。此外,目前的使用者 ID、群組 ID、使用者名稱和群組名稱均未指定。未指定補充群組 ID 集。

目前的使用者 ID 和群組 ID 必須有對應的名稱,可透過 getpwuid() 和 getgrgid() 擷取。但補充群組 ID 可能不適用。

目前使用者必須有主目錄。可能無法寫入。測試不得嘗試寫入該檔案。

網路

未指定主機名稱。可能含有點,也可能沒有。解析主機名稱時,必須提供目前主機的 IP 位址。解析第一個點後截斷的主機名稱也必須正常運作。主機名稱 localhost 必須可解析。

其他資源

測試至少會獲得一個 CPU 核心。其他方法或許也適用,但我們無法保證。未指定這個核心的其他效能層面。如要增加預留的 CPU 核心數量,請在測試規則中加入「cpu:n」標記 (其中 n 為正數)。如果機器的 CPU 核心總數少於要求,Bazel 仍會執行測試。如果測試使用分片,每個分片都會保留此處指定的 CPU 核心數量。

測試可能會建立子程序,但不會建立程序群組或工作階段。

測試可使用的輸入檔案數量有限。這項限制可能會變更,但目前範圍為數萬個輸入內容。

時間與日期

未指定目前的日期和時間。未指定系統時區。

X Windows 可能無法使用。需要 X 伺服器的測試應啟動 Xvfb。

測試與檔案系統的互動

除非另有指定,否則測試環境變數中指定的所有檔案路徑都會指向本機檔案系統中的某個位置。

測試應只在 $TEST_TMPDIR 和 $TEST_UNDECLARED_OUTPUTS_DIR (如有設定) 指定的目錄中建立檔案。

這些目錄一開始會是空白的。

測試不得嘗試移除、chmod 或以其他方式變更這些目錄。

這些目錄可能是符號連結。

「$TEST_TMPDIR/.」的檔案系統類型仍未指定。

測試也可能會將 .part 檔案寫入 $TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR,以註解未宣告的輸出檔案。

在極少數情況下,測試可能會強制在 /tmp 中建立檔案。舉例來說,Unix 網域通訊端的路徑長度限制通常需要建立 /tmp 下的通訊端。Bazel 無法追蹤這類檔案;測試本身必須確保密封性,使用不重複的路徑,避免與其他同時執行的測試和非測試程序發生衝突,並清除在 /tmp 中建立的檔案。

部分熱門測試架構 (例如 JUnit4 TemporaryFolder 或 Go TempDir) 有自己的方式,可在 /tmp 下建立暫時目錄。這些測試架構包含清除 /tmp 中檔案的功能,因此即使這些架構會在 TEST_TMPDIR 以外的位置建立檔案,您仍可使用。

測試必須透過runfiles機制存取輸入內容,或透過執行環境的其他部分存取輸入內容,這些部分專門用於提供輸入檔案。

測試不得存取建構系統的其他輸出內容,路徑是從測試可執行檔的位置推斷而來。

執行檔樹狀結構是否包含一般檔案、符號連結或兩者混合,則未指定。執行檔樹狀結構可能包含目錄的符號連結。 測試應避免在 runfiles 樹狀結構中使用含有 .. 元件的路徑。

執行檔樹狀結構中的任何目錄、檔案或符號連結 (包括遍歷符號連結的路徑) 都不應可寫入。(因此,初始工作目錄不應可寫入)。測試不得假設任何部分的執行檔可寫入,或由目前使用者擁有 (例如 chmod 和 chgrp 可能會失敗)。

執行測試時,執行檔樹狀結構 (包括遍歷符號連結的路徑) 不得變更。父項目錄和檔案系統掛接不得以任何方式變更,以免影響解析執行檔樹狀結構中路徑的結果。

為避免提早結束,測試可能會在啟動時,於 TEST_PREMATURE_EXIT_FILE 指定的路徑建立檔案,並在結束時移除。如果 Bazel 在測試完成時看到該檔案,就會假設測試過早結束,並將其標示為失敗。

執行平台

測試動作的執行平台是透過工具鍊解析決定,與其他動作相同。每項測試規則都有隱含定義的 test exec 群組,除非遭到覆寫,否則該群組在 @bazel_tools//tools/test:default_test_toolchain_type 上有強制性的工具鍊需求。

這類工具鍊不會以供應器形式攜帶任何資料,但可用於影響測試動作的執行平台。

Bazel 會註冊兩個這類工具鍊,如果使用者未明確定義自己的工具鍊,就會生效:

  • 如果啟用 --@bazel_tools//tools/test:incompatible_use_default_test_toolchain (預設),則有效測試工具鍊為 @bazel_tools//tools/test:default_test_toolchain。這個工具鍊需要執行平台,才能符合所有測試規則的目標平台限制。

    具體來說,如果目標平台也註冊為執行平台,就與這個工具鍊相容。如果找不到這類平台,測試規則會失敗,並顯示工具鍊解析錯誤

  • 如果 --@bazel_tools//tools/test:incompatible_use_default_test_toolchain 遭停用,則有效測試工具鍊為 @bazel_tools//tools/test:legacy_test_toolchain。這個工具鍊不會施加任何限制,因此系統會為第一個註冊的執行平台設定測試動作,且不會手動指定 exec 限制。

    在多平台建構作業中,這通常不是預期行為,因為這可能會導致在 Windows 電腦上為 Linux 建構的測試二進位檔,在 Windows 上執行。

    這個選項是舊版設定,日後推出的 Bazel 版本將無法使用。

使用者可以為這個類型註冊其他工具鍊,藉此影響這項行為,且這些工具鍊的優先順序會高於預設工具鍊。測試規則作者可以定義自己的測試工具鍊類型,並為其註冊預設工具鍊。

標記慣例

測試規則中的部分標記具有特殊意義。 另請參閱 Bazel Build Encyclopedia 的 tags 屬性。

標記 意義
exclusive 同時不執行其他測試
external 測試有外部依附元件,請停用測試快取
large test_suite 慣例;大型測試套件
manual * 請勿在萬用字元目標模式中加入測試目標,例如 :...、:* 或 :all
medium test_suite 慣例;中等測試套件
small test_suite 慣例;一組小型測試
smoke test_suite 慣例;表示應在將程式碼變更提交至版本管控系統前執行

Runfiles

在下文中,假設有標示為 //foo/bar:unittest 的 *_binary() 規則,且該規則在執行階段依附於標示為 //deps/server:server 的規則。

位置

目標 //foo/bar:unittest 的執行檔目錄是 $(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles 目錄。這個路徑稱為「runfiles_dir」。

依附元件

執行檔目錄會宣告為 *_binary() 規則的編譯時間依附元件。runfiles 目錄本身取決於影響 *_binary() 規則或任何編譯時間或執行階段依附元件的一組 BUILD 檔案。修改來源檔案不會影響 runfiles 目錄的結構,因此不會觸發任何重建作業。

目錄

runfiles 目錄包含下列項目:

  • 執行階段依附元件的符號連結:每個 OutputFile 和 CommandRule 都是 *_binary() 規則的執行階段依附元件,在 runfiles 目錄中以一個符號連結表示。符號連結的名稱為 $(WORKSPACE)/package_name/rule_name。舉例來說,伺服器的符號連結會命名為 $(WORKSPACE)/deps/server/server,完整路徑則為 $(WORKSPACE)/foo/bar/unittest.runfiles/$(WORKSPACE)/deps/server/server。符號連結的目的地是 OutputFile 或 CommandRule 的 OutputFileName(),以絕對路徑表示。因此,符號連結的目的地可能是 $(WORKSPACE)/linux-dbg/deps/server/42/server。