iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
AI Engineering

一天一個 PR,讓我告訴你嵌入式開發的殘酷:如何為 AI Agent 建立可信的工程閉環系列 第 10

Day 10 - Case 已經搬家,Audit 還在舊 Repo 找:Tests 全綠也沒發現

  • 分享至 

  • xImage
  •  

系列說明 >> 本系列大部分 PR 來自私人或公司專案,部分程式碼、環境設定與實作細節不便公開。

我只能保證文中提到的 PR 與問題皆為真實案例,但會經過必要的匿名化與內容調整。

本系列主要分享問題如何被發現、背後的思考方式,以及解法如何形成,不會深入討論完整實作與部署細節,敬請見諒。

Today’s Change

  • Base Repo: testpilot-core
  • Change Ref: Private wifi_llapi PR #128-fix(audit): P0 pipeline 佈局漂移修復
  • Issue: Repo Split 後,正式 Case 已搬進獨立的 wifi_llapi Repo,但 Audit 與提交前檢查仍在舊 Monorepo 路徑找檔案;真實流程失敗,Tests 卻持續全綠。
  • Root Cause: Audit 的不同階段各自計算 Case 路徑,Tests 又照舊 Layout 建立臨時 Repo;沒有人固定「這次 Audit 實際操作哪一個 Repo」。
  • Solution: 建立 Audit Run 時先確認 Repo 與 Case 的完整位置,寫進 RID;後續階段全部沿用。提交前檢查與 Tests 也改成只認目前的 Repo Layout。
  • Evidence: 修正前可重現 cases directory not foundcases_dir_not_found;修正後 Audit Tests 199 passed、Full Suite 1899 passed,並以真實 Workbook 完成不需硬體的 audit init

先講清楚:這不是同一個 Repo 裡換資料夾

上一篇最後,我只寫了 plugins/wifi_llapi/cases/wifi_llapi/cases/ 兩條相對路徑。

現在回頭看,這樣確實很容易讓人以為,只是在同一個 Repo 裡把 plugins/ 拿掉。其實不是。

把本機目錄簡化成 ~/work/ 後,搬家前是這樣:

cd ~/work/testpilot
realpath plugins/wifi_llapi/cases

# ~/work/testpilot/plugins/wifi_llapi/cases

當時 wifi_llapi 還住在 TestPilot Monorepo 裡。Repo Split 之後則是:

cd ~/work/wifi_llapi
realpath wifi_llapi/cases

# ~/work/wifi_llapi/wifi_llapi/cases

前一個 wifi_llapi 是獨立 Repo 的目錄名稱,後一個才是 Repo 裡真正放 Case 的 package 目錄。

這次不是同一間房子裡換房間,而是從 testpilot Repo 搬到 wifi_llapi Repo。兩條相對路徑單獨拿出來看,剛好把最重要的「Repo 已經換了」藏掉了。

然後老Go直接改了一條正式 Case YAML。

照 D8 建立的規則,正式 Case 如果沒有對應的 Audit Record,提交前的 Hook 應該擋下來。結果什麼都沒有發生。

老Go等了兩秒,又多等了一秒。

「不是說會擋?」

Agent 把 Hook 的規則和實際檔案放在一起:

Hook 還在看:plugins/wifi_llapi/cases/D*.yaml
實際修改的是:~/work/wifi_llapi/wifi_llapi/cases/D*.yaml

「所以我剛才不是 Bypass?」老Go問。

「不是。」Agent 回答,「這個檔案根本沒有進入檢查範圍。」

Bypass 至少表示 Gate 看見了檔案,只是有人明確要求放行。現在比較乾脆,Gate 還很認真,只是守錯 Repo。

Hook 失聯,只是第一個症狀

我原本以為,把 Hook 的 Path 改掉就可以收工。

Case 搬家,警衛跟著搬。Perfect。

老Go也點頭。

「Search、Replace、跑 Tests。這次總不會又長成一套系統吧?」

我先把 Audit 從頭跑一次。

cd ~/work/wifi_llapi

testpilot audit init wifi_llapi \
  --workbook ~/work/Audit_Test_Report.xlsx

預期是建立 RID,開始這次 Audit。實際上,舊程式把目前的 Repo 加上以前的 Monorepo 路徑,組成了另一個根本不存在的地址:

舊程式找到的:~/work/wifi_llapi/plugins/wifi_llapi/cases/
真正存在的是:~/work/wifi_llapi/wifi_llapi/cases/
執行結果:      cases directory not found

「所以不只 Hook?」我問。

RID 建好後,Audit 通常會接著跑 pass12。名字看起來像內部代號,其實就是 Pass 1 加 Pass 2,先用兩輪機械方式縮小需要人工判斷的範圍:

Pass 1:用既有 Case 執行,對照 Workbook
Pass 2:對不上的 Case,再抽出 Workbook 裡可逐字回查的 Command 重試
仍無法確認:留給後續人工或 Agent

它不負責決定 Case 最後該怎麼改,只是先把能直接比對的部分篩掉。

但這次連篩選都還沒開始。audit init 自己算一次 Case 位置,pass12 執行時又算一次,然後把每條 Case 分進 [block] <case-id>: cases_dir_not_foundapply 還有自己的算法。

也就是說,一次 Audit 從開始到寫回正式 Case,每一站都會重新回答:Case 到底在哪裡?

而且大家很有默契地回答同一個舊地址。

這時做全域 Search/Replace,確實能把今天看到的字串改完。但下一個 Command 只要再自己拼一次 Path,同一種問題還是會回來。

真正該修的,不是某一條路徑,而是不要再讓每一站各猜一次。

Target

Tests 為什麼還是綠的?

到這裡,我最不懂的是 Tests。

真實的 audit init 已經找不到 Case,後面的兩輪篩選也整批 Block,為什麼 Audit Tests 還能全綠?

Agent 打開那段「每次跑 Test 前,先替自己搭一個臨時 Repo」的程式。這種測試前置環境通常叫 Test Fixture。

它搭的是:

<tmp>/repo/
└── plugins/
    └── wifi_llapi/
        └── cases/

也就是 Repo Split 以前的 Layout。

舊 Code 去舊地址找,Fixture 剛好在舊地址放好 Case。兩邊配合得非常好,全部 PASS。

老Go看著畫面。

「所以 Tests 沒有說謊?」

「沒有。」Agent 回答,「它們證明舊 Layout 裡的 Audit 可以工作。」

問題是,Production 已經不住那裡了。

我們不是沒有測 Path,而是很完整地測了已經退役的 Path。這就是這次最危險的假綠:Code 和 Tests 完全一致,所以沒有任何紅燈提醒我們,它們一起活在過去。

Target

地址只決定一次,後面不要再猜

PR #128 的修法,其實沒有故事前半段看起來那麼複雜。

audit init
  → 確認 Repo:  ~/work/wifi_llapi
  → 確認 Cases: ~/work/wifi_llapi/wifi_llapi/cases
  → 把完整位置寫進這次 RID

pass12(前兩輪篩選)/ apply / PR 準備
  → 讀同一個位置,不再重新拼 Path

老Go問:「抽成一個共用 Function 不就好了?」

「可以少掉重複 Code。」Agent 說,「但每個階段還是可能在不同工作目錄重新算出不同答案。」

這次多做的一步,是把答案固定在這一次 Run 裡。

RID 以前已經負責把 Workbook、Case 與 Evidence 綁在同一次 Audit。現在它再多記一件最基本的事:這次到底在哪個 Repo 工作。

它沒有證明 Case 內容一定正確,也沒有防止所有版本漂移。它先保證同一個 RID 不會從 A Repo 讀 Case,最後跑去 B Repo 寫回。

這個邊界夠了。

至少今天先不要再替明天的 PR 加班。

Gate 和 Tests 也得真的搬家

Audit 能找到 Case 之後,提交前的 Hook 也改成檢查目前的位置:

Hook Target:      wifi_llapi/cases/D*.yaml
沒有 Audit Record:擋下
明確例外:         [audit-bypass: <reason>]

CI 則先保留 Warning,因為 CI 不一定持有本機產生的 Audit Record。這次沒有假裝一個 PR 就把所有環境的 Evidence 流通問題一起解完。

Tests 的修法也很直接:

只建立:   <root>/wifi_llapi/cases/
故意不建: <root>/plugins/wifi_llapi/cases/

接著把 audit initpass12(前兩輪篩選)到 apply 走完。以後只要其中一段又偷偷回去找舊地址,Test 不會再替它把祖厝蓋回來。

Target

這次證明的是路找對了,不是 415 條 Case 都對了

修正後的驗證結果是:

Audit Tests: 199 passed
Full Suite:  1899 passed

另外也用真實的 Audit_Test_Report.xlsx 執行 audit init,確認能在新的 Repo Layout 建立 RID;沒有 Audit Record 的正式 Case 修改也會被 Hook 擋下。

這次沒有動硬體。原因很簡單:修的是 Repo 路徑、Audit Run 與提交前檢查,不是 DUT 行為。把 DUT、STA、Serial 全部搬出來,不會讓錯的 Path 變得更正確。

所以這些 Evidence 能證明:Audit 現在找得到正式 Case,後續階段使用同一個位置,Tests 也不再依賴舊 Layout。

它不能證明 415 條 Case 已經全部校正正確,也不能證明所有 CI 已經擁有和本機一樣完整的 Audit Record。

老Go看完最後一輪結果。

「所以現在可以開始 Audit 了?」

「至少不會再跑去舊 Repo 敲門。」

「這聽起來只是基本要求。」

「對。」

「那你們前面在高興什麼?」

我沒有回答。

因為門找回來後,下一個浪費時間的地方也出現了。

Workbook 裡有些列,在碰硬體以前就已經知道不需要跑。我們卻還是很勤勞地搬出 DUT、STA 與整套 Testbed,再請硬體證明今天真的不用測。

下一篇,先別急著燒板子。

Have a nice day.


上一篇
Day 9 - 明明叫 Plugin,為什麼愈來愈像 Wi-Fi 專用工具?用拆 Repo 做到乾淨的權責分界
系列文
一天一個 PR,讓我告訴你嵌入式開發的殘酷:如何為 AI Agent 建立可信的工程閉環10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言