測試執行環境的完整規格。
背景
Bazel BUILD 語言包含多種規則,可用於定義多種語言的自動測試程式。
測試是使用 bazel test
執行。
使用者也可以直接執行測試二進位檔。我們允許這麼做,但不建議,因為這類叫用方式不會遵守下述規定。
測試應為密封狀態,也就是說,測試只能存取已宣告依附元件的資源。如果測試並未適當密封,就無法提供可重現的歷來結果。這可能會對找出罪魁禍首 (判斷哪些變更導致測試失敗)、發布工程稽核能力,以及測試的資源隔離 (自動化測試架構不應 DDOS 伺服器,因為某些測試會與伺服器通訊) 造成重大問題。
目標
本頁面的目標是正式建立 Bazel 測試的執行階段環境和預期行為。也會對測試執行器和建構系統設下限制。
測試環境規格可協助測試作者避免依賴未指定的行為,因此測試基礎架構可更自由地進行實作變更。這項規格會填補一些漏洞,目前許多測試雖然並非完全密封、具決定性且可重入,但仍可通過。
本頁面旨在提供規範和權威資訊。如果這項規格與測試執行程式的實作行為不一致,則以規格為準。
建議規格
「MUST」、「MUST NOT」、「REQUIRED」、「SHALL」、「SHALL NOT」、「SHOULD」、「SHOULD NOT」、「RECOMMENDED」、「MAY」和「OPTIONAL」等關鍵字應按照 IETF RFC 2119 的說明解讀。
測試目的
Bazel 測試的目的是確認簽入存放區的來源檔案是否具有某些屬性。(在本頁面中,「來源檔案」包括測試資料、黃金輸出內容,以及版本控管下的任何其他內容)。一位使用者編寫測試,用來判斷預期會維持不變的項目。其他使用者稍後會執行測試,確認不變量是否已遭破壞。如果測試取決於來源檔案以外的任何變數 (非密封),其價值就會降低,因為測試停止通過時,後續使用者無法確定變更是不是導致問題的原因。
因此,測試結果只能取決於:
- 測試聲明依附的來源檔案
- 測試已宣告依附的建構系統產品
- 測試執行器保證行為保持不變的資源
目前不會強制執行這類行為。不過,測試執行器保留日後新增這類強制執行的權利。
建構系統的角色
測試規則與二進位規則類似,兩者都必須產生可執行的程式。對於某些語言,這是將語言專屬的測試架構與測試程式碼結合的虛設常式程式。測試規則也必須產生其他輸出內容。除了主要測試可執行檔,測試執行器還需要runfiles資訊清單、應在執行階段提供給測試的輸入檔案,以及測試的類型、大小和標記相關資訊。
建構系統可能會使用執行檔傳送程式碼和資料。(這項功能可用於最佳化作業,透過在測試之間共用檔案 (例如使用動態連結),縮小每個測試二進位檔的大小)。建構系統應確保產生的可執行檔透過測試執行器提供的 Runfile 映像檔載入這些檔案,而不是對來源或輸出樹狀結構中的絕對位置進行硬式編碼參照。
測試執行工具的角色
從測試執行工具的角度來看,每項測試都是可透過 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
指定檔案的上次修改日期。否則,如果啟用 --incompatible_check_sharding_support
,Bazel 會在測試分割時導致測試失敗。
初始條件
執行測試時,測試執行器必須建立特定初始條件。
測試執行工具必須使用 argv[0]
中的測試可執行檔路徑,叫用每項測試。這個路徑必須是相對路徑,且位於測試的目前目錄下方 (位於 runfiles 樹狀結構中,請參閱下文)。除非使用者明確要求,否則測試執行器不應將任何其他引數傳遞至測試。
初始環境區塊應包含下列內容:
變數 | 值 | 狀態 |
---|---|---|
HOME |
$TEST_TMPDIR 的值 |
建議 |
LANG |
未設定 | 必填 |
LANGUAGE |
未設定 | 必填 |
LC_ALL |
未設定 | 必填 |
LC_COLLATE |
未設定 | 必填 |
LC_CTYPE |
未設定 | 必填 |
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
可能會失敗)。
執行測試時,執行檔樹狀結構 (包括遍歷符號連結的路徑) 不得變更。父項目錄和檔案系統掛接不得以任何方式變更,以免影響在 Runfiles 樹狀結構中解析路徑的結果。
為及早發現提早結束的情況,測試可能會在啟動時建立 TEST_PREMATURE_EXIT_FILE
指定路徑的檔案,並在結束時移除該檔案。如果 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
的 Runfiles 目錄是 $(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
。 - 子執行檔的符號連結:對於每個屬於
*_binary()
C 執行階段依附元件的*_binary()
Z,C 的執行檔目錄中會有第二個連結,指向 Z 的執行檔。符號連結的名稱為$(WORKSPACE)/package_name/rule_name.runfiles
。符號連結的目標是 runfiles 目錄。舉例來說,所有子程式都會共用一個通用執行檔目錄。