一個混合 Swift、Objective-C 與 C/C++ 相依套件的 iOS 專案,即使一般測試連續通過,仍可能在某條低頻執行路徑發生陣列越界、重複釋放或釋放後使用。把工作移到雲端 Mac 後,不要只是重複執行原有測試;更有效的做法是新增一條 Address Sanitizer(ASan)流水線,讓非法記憶體存取直接中止建置,並保留可重現問題的結果套件。
先確定門禁要攔截什麼
ASan 主要攔截堆疊越界、釋放後使用、重複釋放,以及部分無效指標存取。它特別適合檢查 Objective-C 橋接層、影像處理函式庫、資料庫封裝與手寫 C 介面。純 Swift 程式碼也能受益,但單次通過不代表不存在記憶體問題:測試未執行到的分支不會受到檢查,而記憶體洩漏與資料競爭也需要其他工具補充。
建議先選擇一組穩定且不需要人工互動的測試,作為門禁入口,不要一開始就把所有 UI 測試都塞進來。核心解析器、快取層與跨語言介面的優先級最高。
| 測試層級 | 執行頻率 | 失敗處理 |
|---|---|---|
| 核心單元測試 | 每次合併請求 | 阻止合併 |
| 整合測試 | 主分支或合併請求 | 阻止發布 |
| 完整 UI 測試 | 定時執行或候選版本階段 | 建立缺陷並重現 |
Sanitizer 的價值不只是讓工作顯示失敗,而是讓相同輸入能再次觸發同一個錯誤,並留下呼叫堆疊、測試名稱與建置環境。
建立獨立的 Scheme 與目錄
在 Xcode 中複製現有的測試 Scheme,命名為 App-ASan,並將它設為 Shared。不要直接修改開發人員日常使用的 Scheme,否則本機偵錯、效能測量與 CI 參數會互相干擾。Scheme 應提交至:
App.xcodeproj/xcshareddata/xcschemes/App-ASan.xcscheme
每次工作都使用獨立的 DerivedData。這樣可以避免一般建置產生的目的檔被誤用於 ASan,也能防止兩個平行工作爭用索引與中間產物。結果套件也應依工作編號分目錄保存,而不是反覆覆寫固定檔案。
固定測試目標
先查看目前可用的模擬器:
xcrun simctl list devices available
在 CI 中,最好固定裝置類型與系統主版本,同時允許系統修補版本隨已安裝的 runtime 更新。若團隊必須驗證多個系統版本,應拆成矩陣工作,不要在單一命令中依賴自動選擇。
使用 xcodebuild 執行檢查
以下指令碼會使用獨立的建置目錄與結果套件。工作區、Scheme 與模擬器名稱應改成專案的實際值。
#!/bin/zsh
set -u
ROOT="${PWD}"
OUT="${ROOT}/artifacts/asan"
DERIVED="${ROOT}/.ci/DerivedData-ASan"
rm -rf "${OUT}" "${DERIVED}"
mkdir -p "${OUT}"
xcodebuild test \
-workspace App.xcworkspace \
-scheme App-ASan \
-configuration Debug \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-derivedDataPath "${DERIVED}" \
-resultBundlePath "${OUT}/App-ASan.xcresult" \
-enableAddressSanitizer YES \
CODE_SIGNING_ALLOWED=NO \
>"${OUT}/xcodebuild.log" 2>&1
status=$?
exit ${status}
不要在測試命令後加上會無條件成功的 || true。若 CI 平台需要後處理日誌,應先保存結束碼,完成封存後再原樣回傳。CODE_SIGNING_ALLOWED=NO 適合模擬器測試;涉及實體裝置或封存的工作應使用獨立的簽署設定,不能直接照搬。
ASan 會增加記憶體用量與執行時間,因此同一台機器的並行數應低於一般單元測試。若程序遭系統終止,應先降低並行數,再判斷是否為受測應用程式的缺陷。
讓失敗證據可以交接
工作失敗時,至少要保存 xcodebuild.log 與 .xcresult。前者便於搜尋 Sanitizer 報告,後者包含測試層級、失敗附件與活動日誌。封存前可以先執行一次完整性檢查:
test -s artifacts/asan/xcodebuild.log
test -d artifacts/asan/App-ASan.xcresult
xcrun xcresulttool get test-results summary \
--path artifacts/asan/App-ASan.xcresult \
> artifacts/asan/summary.json
不同 Xcode 版本的 xcresulttool 子命令可能有所變動,因此工具呼叫方式應與 Xcode 版本一併維護。命令不相容時,也不能因此刪除原始結果套件。
排查報告時,先尋找 ERROR: AddressSanitizer,再記錄錯誤類型、第一個業務程式碼堆疊框架、觸發問題的測試與輸入資料。位於頂端的系統函式庫堆疊框架通常只表示當機發生的位置,真正造成記憶體損壞的寫入動作可能早已發生。若錯誤無法穩定重現,先單獨循環執行對應測試,並停用隨機測試順序,避免同時改變多個變因。
分層執行並處理常見誤區
日常門禁可以只執行高風險測試集合,再由主分支執行完整的 ASan 測試套件。使用測試標籤或 Test Plan,比依檔名拼接命令更容易維護。新增 C 介面、指標運算或不安全的緩衝區處理時,應同步將相關測試加入快速集合。
常見誤區有三類。第一,重複使用一般建置快取,導致插樁不完整;第二,只上傳文字日誌,卻沒有保存結果套件;第三,把 ASan 失敗視為偶發測試而直接重試。重試可以確認問題是否穩定,但第一次報告仍應保留;即使連續重試通過,也不能自動放行已捕捉到的非法存取。
最後要明確寫出驗收條件:測試程序的結束碼為零、結果套件存在、日誌中沒有 ASan 錯誤,而且工作未因逾時或資源壓力而中止。如此一來,門禁判斷的是完整執行結果,而不是一行看似成功的測試摘要。
常見問題
Address Sanitizer 應該在每次提交時執行嗎?
核心單元測試與高風險模組適合每次執行;耗時較長的完整 UI 測試可安排在合併請求、主分支或排程工作中。
Address Sanitizer 通過就代表沒有記憶體問題嗎?
不代表。它只檢查本次實際執行的路徑及特定錯誤類型,仍需搭配洩漏分析、資料競爭檢查與實機驗證。
依任務長度選擇雲端 Mac 租用週期
兩種 M4 設定可按日、週、月或季租用;節點與實際可用資訊以控制台即時回傳內容為準。