RFC-0256:Lacewing 測試專用的 Python 應用程式組合

RFC-0256:針對 Lacewing 測試的 Python 應用程式組合
狀態已接受
區域
  • 建構
  • 軟體推送
  • 測試
  • 工具鏈
說明

建議使用已編譯的 Rust 主機自解壓縮二進位檔,將 Python Lacewing 測試應用程式打包。

Gerrit 變更
作者
審查人員
提交日期 (年-月-日)2024-05-22
審查日期 (年-月-日)2024-08-08

摘要

建議將 Lacewing 測試做為編譯的 Rust 主機二進位檔,以滿足 Fuchsia 近期的測試特定 Python 應用程式捆綁和發布需求 (例如驅動程式相容性、CTF)。

本提案並未排除日後探索替代方案的可能性。

提振精神

由於最近在 Fuchsia IDK 中新增了 RTC 驅動程式庫相容性測試,我們現在有了可運作且可承載負載的機制,可為樹狀結構外 (OOT) 執行作業提供版本化的樹狀結構內 Python Lacewing 端對端測試。

將 Lacewing 測試發布給 SDK 使用者的能力,可直接支援 Fuchsia 2024 路線圖 (即 Driver 相容性測試)。此外,其他需要以版本包裝和發布的測試工作 (例如平台預期測試和主機工具相容性測試) 也能從中受益。

在擴大採用這項測試發布模式之前,我們必須確保這項模式可持續運作,並提前解決潛在的技術債務。具體來說,目前要求下游 Lacewing 測試使用者提供自己的 Python 執行階段來執行測試,容易發生錯誤。因此,這項提案會完全移除這項要求,以解決這個缺點。

相關人員

協助人員:

  • abarth@google.com

審查者:

  • abarth@google.com
  • chaselatta@google.com
  • hjfreyer@google.com
  • keir@google.com
  • tmandry@google.com
  • tonymd@google.com

諮詢:

  • awdavies@google.com
  • crjohns@google.com
  • cpu@google.com
  • jamesr@google.com

社會化:

我們透過 Google 文件與 FEC、Pigweed、Toolchain、SDK 體驗和工具團隊的成員分享這項提案。我們也在 FEC 會議中討論了這項提案,並支持正式的 RFC 批准程序。

目標

  • 在 Lacewing 測試二進位檔執行 OOT 時,消除 Python 執行階段相容性問題
  • 將 Lacewing Python 測試封裝為單一密封可執行檔
    • 支援 C 擴充功能程式庫 (例如 Fuchsia Controller)
    • 支援資料依附元件 (例如 FIDL IR 檔案 - 目前由 Fuchsia 控制器所需)
  • 支援在定義 Lacewing 測試時將其捆綁
  • 支援 Linux 主機環境

非目標

  • 支援 Windows 主機環境
  • 支援 Mac 主機環境

提案

我們建議使用以 Rust 為基礎的自訂方法,滿足 Fuchsia 的即時 Python 應用程式捆綁需求,因為這種方法既可行,也符合所有目標

總覽

執行 Lacewing 測試所需的所有 Python 元件都會以資料資源的形式嵌入已編譯的 Rust 二進位檔 (透過 Rust 的 include_bytes! 巨集在二進位檔中內嵌)。執行時,Rust 二進位檔會將 Python 內容擷取至暫時目錄,建構指令,然後執行 Python 應用程式。

與領先的替代方案 PyInstaller 相比,我們目前偏好採用自訂的 Rust 方法,因為這類方法可靈活地用於 Fuchsia.git (詳情請參閱 PyInstaller 的拒絕理由部分)。以下摘要說明這兩種方法的主要取捨:

  • 以自訂 Rust 為基礎的方法的優點:

    • 可在 Fuchsia.git 中使用
    • 實作,並在本機和基礎架構環境中成功執行
    • 採用 Fuchsia 現有 Rust 主機工具鍊的密封性保證
    • 不需要額外維護動態連結的 Python 執行階段
    • 無須更新 Fuchsia 控制器即可定位嵌入式 FIDL-IR 檔案
  • PyInstaller 的優點:

    • 內建所有 Python 應用程式的通用支援
    • 支援非 Linux 主機平台 (例如 Windows) 的視線更佳

實作

Lacewing Rust 二進位檔包含 3 個邏輯區段:Lacewing 構件嵌入、構件擷取和測試執行。

嵌入

執行 Lacewing 測試所需的所有元件都會以單一封存檔的形式嵌入 Rust 二進位檔。

目錄

嵌入式封存檔案包含下列項目:

  • 靜態連結的 Python 執行階段及其標準程式庫 (Python 模組)
  • 以 PYZ 格式呈現的 Lacewing 測試 (與 Fuchsia.git 中使用的 zipapp 捆綁相符)
  • Fuchsia 控制器共用程式庫
  • FIDL IR 檔案 (目前為 Fuchsia Controller 所需)

壓縮

為求簡單,上述所有內容都會壓縮為單一封存檔案。目前的實作方式採用 ZIP 格式,可產生大約 40 MB 的經過精簡的 linux-x64 主機二進位檔。日後的迭代作業可能會選擇壓縮率較高的壓縮格式,例如 zstd (在主機驅動的系統測試中,壓縮效能不如重要)。

建構

為了將任意 Lacewing 測試套件打包,我們會新增 GN 建構自動化功能,以便動態提供 Lacewing 構件供嵌入。

我們是透過結合 rustc_embed_files()rustc_binary() GN 範本來達成這項目標,前者會以 Rust 程式庫的形式提供測試專屬資料資源,而後者則包含與測試無關main() 邏輯,用於擷取和執行

擷取及執行

執行時,Rust 二進位檔會先在暫存目錄中解開內嵌的資源。接著,系統會參照已擷取的內容,建立執行 Lacewing 測試的指令。最後,指令會執行,並且 Rust 二進位檔會結束。為了確保 hermiticity,我們也會在指令中設定 PYTHONPATH 環境變數,確保不會使用環境 Python 安裝和程式庫。

成效

建構時間:Lacewing 測試的綁定作業只會在 Fuchsia 的 Lacewing 測試組合中執行一小部分,因此 Rust 二進位檔綁定作業的建構額外負擔可忽略不計。

執行時間:由於 Python 應用程式已綁定 E2E 測試性質,因此在擷取階段發生的輕微啟動負擔,對這些應用程式而言並非敏感問題。實證基礎架構資料顯示,未經最佳化的版本為 <6 秒,但這些測試的平均執行時間約為 1 分鐘,因此這項差異可以忽略不計。

大小:輸出可執行檔大小約為 40 MB,與 PyInstaller 等替代方案相似。不過,在嵌入期間使用的壓縮演算法仍有改善空間。

以下是可改善上述各維度效能的機會,隨著綁定的 Lacewing 測試數量增加,您可以探索這些機會:

  • 建構時間:建構「主體」Rust ELF 二進位檔,其中包含與測試無關的構件 (例如執行階段、標準庫、C 擴充功能),並將其與測試專屬的 ZIP 封存檔 (例如 test.pyz) 連結。接著,「stem」ELF 就可以透過 ZipArchive 自行解壓縮,以便存取其 Zip 內容,就像目前建議的方法一樣;只是 Rust 編譯程序只會執行一次,而非針對每個捆綁的 Lacewing 測試執行。

  • 執行時間:使用 GN 設定來改善 Rust 編譯速度。您可以評估擷取時間,並將結果匯出至效能追蹤後端,以防回歸。

  • 大小:與上述建構時間最佳化相同,將 Rust ELF 二進位檔拆分為與測試無關的「主體」和測試專屬的 Zip 封存檔,我們就能將大型「主體」與較小的測試專屬 Zip 封存檔分開發布。這樣一來,我們就能節省儲存空間和網路頻寬,因為在每個捆綁的 Lacewing 測試中,都會重複加入「stem」詳情請參閱「可執行檔大小」。

人體工學

這項提案可改善 OOT Lacewing 測試執行作業的使用者體驗,整合者現在只需啟動單一測試可執行檔即可。

回溯相容性

新增密封模式 (Rust 捆綁) 時,無須變更 Lacewing 測試來源。換句話說,Lacewing 測試可以在密封 (./test) 和非密封 (./python test.pyz) 模式下執行,而無須變更 Python 原始碼。

測試

1) Rust 封裝程序和 2) 產生的密封測試,兩者的穩定性都會在 Fuchsia 基礎架構中進行浸潤測試。我們會將測試穩定性與非密封的對應項目進行基準測試;如果在異常/失敗率方面沒有明顯差異,我們會認為以 Rust 為基礎的捆綁方法已可供實際使用 (例如 CQ 和 SDK 發布)。

風險與資源

執行檔大小

假設每個 Lacewing 二進位檔的儲存空間占用量約為 40 MB,如果我們將分散測試的數量調整為合理的預估值 (例如 100 個測試需要 4 GB),這可能會造成疑慮。為降低成本,您可以同時探索下列項目,以便根據捆綁的 Lacewing 測試數量,以亞線性方式調整儲存空間和頻寬成本。

  1. 在單體 Rust 應用程式中分發所有測試。

    • Lacewing 封存檔的最大元件 (Python 執行階段、標準程式庫和 Fuchsia Controller 擴充功能) 會分發一次。
    • 這個做法可節省的大小會隨著我們分發的 OOT Python 應用程式數量而增加。
  2. 獨立發布測試。

    • 與上述情況類似,Lacewing 封存檔中最大的元件會在單一 Rust 應用程式中分發一次。
    • 測試 PYZ (500kB) 會個別分發,並以執行階段引數的形式提供給主要測試應用程式。
  3. 縮減 Fuchsia Controller 的大小。

    • 由於 Fuchsia Controller 會遷移至靜態 Python 繫結 (上游執行階段邏輯至 fidlgen),因此這項作業會自然減少。
      • 將移除 fidl_codec.so
      • 靜態 Python FIDL 繫結會比 FIDL IR 檔案更精簡,因此檔案大小也會比較小
  4. 上文所述,請試試其他壓縮格式和參數。

跨平台可行性

將 Python 套件組合解決方案與 Fuchsia 的 Rust 主機工具鍊結合,可讓我們免費取得 Linux x64 支援,但也限制我們只能使用目前支援的平台。目前,Linux x64 已足以滿足我們當前的需要。不過,如果需要支援其他主機平台 (例如 Windows),我們就需要與 Fuchsia Rust 和 Fuchsia Build 團隊合作,以便評估可行性。

在撰寫本文時,我們尚未發現任何非 Linux-x64 主機平台的 OOT Lacewing 測試需求。

有限支援

目前的提案僅限於支援 Lacewing Python 應用程式。不過,如果有需要,這種做法可以合理地擴充,以支援一般 Python 應用程式,而且比起探索替代方案 (例如 PyInstaller/PyOxidizer),這種做法所需的努力程度要小得多。

如果有重大的規範變更,例如:

  • Windows 支援
  • 位元可複製性
  • 不要在幕後啟動 Python

安全性考量

在 Fuchsia 的 SDK 中發布 Rust 主機二進位檔是經過完善的程序 (例如 ffx),因此在 Lacewing 測試中採用相同做法不會帶來額外的安全性風險。

隱私權注意事項

說明文件

替代方案

PyInstaller

我們特意將這個大篇幅的 PyInstaller 相關內容納入,以供後人參考。以下發現可協助我們決定今天採用自訂的 Rust 方法,並為日後的任何努力提供背景資訊。

PyInstaller 拒絕原因

由於 PyInstaller 的授權,因此只能在 Fuchsia.git 中使用,做為 Fuchsia 開放原始碼授權政策「開發目標」部分下預先建構的構件。不過,PyInstaller 的設計是用來做為來源執行,因此不容易套件成預先建構的項目。我們目前沒有動力解決這個非簡單的問題,因為我們有一個可用的 Rust 解決方案,可樹狀結構內使用,並滿足我們當前的需要。

總覽

PyInstaller 是開放原始碼的第三方 Python 程式庫,可將 Python 應用程式及其依附元件打包成獨立可執行檔,在未安裝 Python 的最終使用者系統上執行。由於 UIt 易於使用且成熟度高 (自 2015 年以來有許多活躍版本),因此是 Python 應用程式封裝領域中較熱門的解決方案之一。

當 PyInstaller 以「one-file」模式執行時,會將啟動時使用的執行階段,連同所有應用程式模組,打包至輸出執行檔。執行輸出可執行檔時,PyInstaller 的啟動載入器會將內嵌封存檔 (包含 Python 轉譯器、內建/使用者提供的 Python 模組、內建/使用者提供的共用程式庫和資料檔案) 擷取至暫時目錄,然後在密封的環境中執行轉譯器。

有了 PyInstaller,我們就能將 Lacewing 測試整合成單一密封可移植的可執行檔 (Python 執行階段 + 測試模組 + 程式庫模組 + Fuchsia Controller C 擴充功能 + 資料依附元件,例如 FIDL IR),以便在各種 Linux 主機環境中執行。

很遺憾,Fuchsia 的供應商 Python 等靜態連結 Python 執行階段,並不相容於 PyInstaller (相關問題:12)。解決方案是在 Chromium 的 3PP 基礎架構 (第三方套件) 中,建構相同 CPython 執行階段的動態連結版本。建構完成後,動態連結的版本 (從此以 DL-CPython3 表示) 即可在 Fuchsia.git 中固定為預先建構的版本,以滿足 Fuchsia 的 Python 應用程式封裝需求。

由於用於建構 DL-CPython3 的原始碼與 Fuchsia 中已使用的靜態連結版本 (從此以後稱為 ST-CPython3) 相同,因此 DL-CPython3 不需要額外的授權核准。

可運作的原型

這個 Fuchsia.git CL 會透過 Fuchsia 的 GN 建構系統,透過 PyInstaller 封裝 Lacewing 測試的可行性。我們使用原型 CL 確認,在 GN 時間點在來源中執行 PyInstaller,即可產生密封的 Lacewing Python 測試可執行檔。

由於原型設計中使用了未提交的依附元件,因此在撰寫本文時,無法展示 CL 在 Fuchsia 基礎架構中運作的情形。不過,我們不認為在 Fuchsia 原始碼中提供這些依附元件後,基礎架構支援會遇到任何問題。

未提交的依附元件
  • DL-CPython3:此項目目前已在 Chromium 3PP 中建構,但尚未在 Fuchsia.git 中固定。

  • PyInstaller 程式庫:PyInstaller 及其間接的 Python 相依元件已完成 OSRB 審查,但只能在 Fuchsia.git 中以預先建構的二進位檔形式使用。

Fuchsia 控制器

您必須更新 Fuchsia Controller,才能在執行階段找出資料依附元件。執行 PyInstaller 可執行檔時,系統會解壓縮其內容,並從暫存目錄執行。有了這個修正程式,Fuchsia Controller 就會支援 PyInstaller,並在以 PyInstaller 可執行檔執行時,在暫時目錄中尋找 FIDL IR。

Hermeticity

經檢查 PyInstaller 的建構中繼資料 (Analysis.toc) 後,我們確認 PyInstaller 的輸出內容是密封的。其中所有內含的內容皆來自 $FUCHSIA_DIR,也就是說,不會包含任何環境系統共用程式庫。

此外,在 PyInstaller 輸出內容上執行 ldd 會顯示一組共用程式庫依附元件,這些依附元件在現代 Linux 發行版本中普遍且可靠地相容。

~/fuchsia$ ldd dist/soft_reboot_test_fc
linux-vdso.so.1
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
/lib64/ld-linux-x86-64.so.2

為避免密封性隨著時間流逝而減弱,您可以新增建構時檢查,確認 PyInstaller 可執行檔的 ldd 輸出內容是否存在於許可清單中。

大小

在原型設計中,如果不考慮大小最佳化,透過 PyInstaller 封裝的軟重開 Lacewing 測試大小約為 43 MB (供您參考,PYZ 版本約為 400 KB)。

我們發現了許多尚未探索的機會,可縮減 PyInstaller Lacewing 測試的大小:

  1. 執行 PyInstaller 時,請使用 UPX 壓縮。
  2. 排除未使用的內建擴充功能模組。

執行階段管理和維護

Fuchsia 團隊需要同時維護 DL-CPython3 (用於版本化 Lacewing 測試封裝) 和 ST-CPython3 (用於其他所有項目),並確保這兩個版本在功能上相同,且會同步更新 (例如共用相同的 CIPD 版本標記)。這需要與 Google 內部 PEEP 軟體部署團隊合作,以便簡化 3PP 設定,讓 2 個版本以程式碼防止差異。

跨平台可行性

PyInstaller 不支援跨平台編譯,因此我們需要在各自的目標環境 (例如 Windows、Mac) 中執行 PyInstaller,產生主機平台專屬的執行檔。

舉例來說,為了支援 Windows 使用者,我們需要在 Windows 機器上使用 DL-CPython3 的 Windows 版本,執行 PyInstaller。在撰寫本文時,Fuchsia 並沒有這類以 Windows 為基礎的建構環境,因此需要設定範圍,才能在建構機隊中新增支援。至於 Windows 版的 DL-CPython3,Chromium 3PP 已支援為多個平台 (包括 Windows) 建構 ST-CPython3,因此我們應該可以輕鬆擴充對 Linux 以外平台的支援。

PyOxidizer

PyOxidizer 是另一個吸引人的一般 Python 應用程式封裝選項,相較於 PyInstaller,它具有以下多項優點:

  • 密封性 - PyOxidizer 的輸出內容是編譯的二進位檔,而 PyInstaller 的輸出內容是需要在執行階段解壓縮至 Python 位元 (例如內建 Python 擴充功能) 的封存檔。
  • 速度:PyOxidizer 的輸出內容不需要解壓縮即可執行,因此啟動時間比 PyInstaller 的輸出內容快。
  • 安全性 - PyOxidizer 是以 Rust 編寫,這項語言提供比 PyInstaller 的 Python 和 C 更完善的安全性保證。

不過,這些優點在 E2E 測試情境中幾乎可以忽略不計。

PyOxidizer 拒絕原因

最終,我們並未選擇 PyOxidizer,因為該工具無法滿足目標,無法在單一可執行檔中綁定 C 擴充功能程式庫。使用 PyOxidizer 的粗略探索無法在輸出可執行檔中內含玩具 C 擴充功能。由於 PyOxidizer 具有高度可設定性,因此實驗可能會有設定錯誤,但由於 PyOxidizer 相對較新穎且採用率較低,因此這類主題的資源較少,因此我們目前已擱置進一步探索。

既有技術與參考資料