系列說明
本系列大部分 PR 來自私人或公司專案,部分程式碼、環境設定與實作細節不便公開。
我只能保證文中提到的 PR 與問題皆為真實案例,但會經過必要的匿名化與內容調整。
本系列主要分享問題如何被發現、背後的思考方式,以及解法如何形成,不會深入討論完整實作與部署細節,敬請見諒。
Change Ref: PR #3 —
fix(wifi_llapi): 強化 full run 指令解析與報告輸出Issue: 單一 UART command 可以正常執行,但進入 Full Run 後,開始出現多命令遺失、Readback 執行錯誤、RC 判斷錯誤與報告汙染。
Root Cause: Test Case、Shell Command、UART Transcript 與執行結果全都混在普通字串裡,系統只能不斷使用 Regex 猜測真正的邊界。
Solution: 強化 Command Extraction、多命令執行與 Readback Fallback;修正 serialwrap RC 擷取;加入 YAML Command Audit 與 Report Sanitization。
Evidence: 完成 415 個官方 Case 的實機 Full Run,Regression Tests 由 38 個增加至 46 個。
上一篇,我們終於讓 Agent 可以透過 serialwrap 對 UART 下 Command,再從 minicom log 裡取得執行結果。
不需要每次重新教 Agent:
minicom 位於哪一個 tmux pane。
send-keys 要怎麼下。
Log 應該從哪裡切。
哪一段 Output 才屬於剛才執行的 Command。
實際用起來也真的不錯。
Agent 終於可以自己到板子上找證據,不再只拿 Source Code 證明自己腦補出來的 Runtime 行為。
這次好像真的閉環了。
有了可以反覆運作的環境,大膽的想法自然會愈來愈多。
那麼,把它接進一套真正的測試流程,看起來似乎也是非常合理的一件事。
嗯……但是。
就是那個 But。
人類最常犯的錯誤,就是看到一個東西能跑一次,便開始覺得它可以跑四百次。
四百次……
四百次……
四百次……
(迴音)
搞測試這件事,從來都是吃力不討好。
看起來好像做了很多事,但最後又很像什麼都沒有做。
在反覆追查、驗證、確認、核實一大堆連我自己都不知道到底對不對的 HAL(Hardware Abstraction Layer,硬體抽象層)時,除了無法獲得任何多巴胺,還會確實喚醒我的懶懶魂。
「凡我所不欲,悉使 Agent。」
這是我在 AI 時代最信奉的一句座右銘。
以前遇到這種工作,我大概會:
寫一個 Bash Script 做 Parser。
寫一個 minicom Script 執行驗證。
一邊看 Code,一邊處理各種例外。
最後發現九成的 Case 本身就是例外。
花了大半個月寫出來的工具,可能還不如直接 K Code,一條一條硬啃。
這是駑鈍如我,在多年硬幹之後得到的普通 RD 沒用心得。
但現在不一樣了。
我有 Agent、有 Gemini、有 serialwrap。
我可以對 Agent 說:
「工具給你,馬上測。」
然後 Agent 就會告訴我:
「結果出來了,87 條 Pass。」
隔天再跑一次:
「結果出來了,78 條 Pass。」
再跑一次:
「結果出來了,8 條 Pass。」
最後一次:
「結果出來了,全數 Pass。」
老兄,不是這樣的吧?
同一條 Case 可以這樣過了又掛、掛了又過嗎?
這份測試結果到底還能不能信?
聰明如你,當然也想到了那句話:
用魔法打敗魔法。
用檢討逼你就範。
用工具強迫你說實話。
出來吧,Harness!我還你原形!
這就是 TestPilot。
一套用來執行 Embedded Test Case 的測試框架。
它會把原本散落在 Excel、YAML 與測試文件裡的內容整理成執行流程,再透過不同的 Transport 操作 DUT、STA 或其他測試設備。
大致上的路徑如下:
Excel / YAML Case
↓
TestPilot Orchestrator
↓
HALAPI Plugin
↓
serialwrap Transport
↓
UART / DUT
↓
Result Parser
↓
Excel Report
單獨看每一層都很合理。
Case 告訴 TestPilot 要做什麼,Plugin 把內容轉成 Command,serialwrap 負責送進 UART,最後再把結果寫回 Report。
執行過程不再讓 Agent 臨場自由發揮。
妥妥地 Vibe 出了一整套自動化測試框架。
Perfect。
直到 Full Run 開始。
既有的 415 條 Case,全數來自 Excel 與各種充滿歷史情懷的測試文件,再由 Agent 協助轉成逐 Case 的 YAML。
這些文件有一個共通點:
它們是寫給人看的,不是寫給 Parser 看的。
例如:
Set all radios transmit power to 50:
ubus-cli WiFi.Radio.1.TransmitPower=50;
ubus-cli WiFi.Radio.2.TransmitPower=50;
ubus-cli WiFi.Radio.3.TransmitPower=50
看到這裡,你知道、我知道,Agent 也知道要執行三條 Command。
但 Parser 不知道。
Plugin 只會從裡面找到第一個看起來可以執行的 Command,清掉前面的自然語言,再交給 serialwrap。
於是,它很認真地執行了:
ubus-cli WiFi.Radio.1.TransmitPower=50
Test PASS。
更精彩的是,第一條 Command 確實成功,Return Code 也是 0,所以整條 Flow 仍然會回報 Success。
只完成了三分之一的工作,卻產出了一份完整的成功報告。
你問我 Radio 2 跟 Radio 3 去哪裡了?
阿災。
可能放假去了吧。
另一類 Case 則會長成這樣:
Verify Associated Device Capabilities
ubus-cli WiFi.AccessPoint.1.AssociatedDevice.1.Capabilities="RRM,BTM,PMF"
人類看得懂它的真正意思:
查詢 DUT 回報的 Capabilities,確認結果是否符合預期。
但 Parser 看到的,是一條語法完整、可以直接執行的 Setter。
於是,它真的把答案寫進 DUT(Device Under Test,待測設備),再確認答案是否正確。
Good job!
這已經不是開書考試了。
是老師先幫考生把答案寫好,再宣布驗證通過。
因此,這次加入了 Readback Fallback。
當 Step 的目的是擷取資料,而且 Case 裡有足夠的 Object 與 Field 資訊時,就不要直接照抄文件裡那段真假難辨的文字,而是重新組出真正的 Query:
ubus-cli "WiFi.AccessPoint.*.AssociatedDevice.*.Capabilities?"
這次也開始從 Step 裡找出多條 Command,依序執行。
其中任何一條失敗,就停在那裡,不再假裝後面的工作已經完成。
同時加入 YAML Command Audit,把藏在 ; 與 && 後面的 Command 先列出來。
注意。
是列出來,不是直接幫我改。
因為:
cmd1 && cmd2
和:
cmd1
cmd2
看起來只是拆成兩行,語意卻可能完全不同。
自動改完四百多條 Case,確實很 Vibe。
也很適合替自己創造下一個月的工作量。
可喜可賀,可喜可賀。
TestPilot 第一次在實質意義上完整跑完了 Full Run Test。
但回頭檢閱 Report 時,Runtime Log 裡卻塞滿了:
BEGIN/END Marker
RC Marker
Shell Prompt
Command Echo
真正的 Output
這些 Marker 是 serialwrap 能夠解析 UART Log 的關鍵手段,卻也成了 TestPilot 最沉重的負擔。
__TP_BEGIN_abcd1234__
command output
__TP_RC_abcd1234__=0
__TP_END_abcd1234__
一份充滿 Marker 的 Log,身為剛好還是人類的我,讀起來真的很痛苦。
多年寫 Code,一朝 Vibe。
天天 Vibe,Vibe 到 Code 都快看不懂了。
我不想連 Log 都一起看不懂啊。
所以這次也把 Marker、Prompt 與多餘空白清掉,Command 欄則改成一行一條。
Report Filename 也加入 run_id,避免下一次 Run 直接覆蓋上一次的測試證據。
畢竟測試的目的是留下證據,不是毀屍滅跡。
Say yes!
這次 PR 修了不少問題:
一個 Step 裡有三條 Command,卻只執行第一條。
明明要做 Readback,卻把文件裡的 Setter 送進 DUT。
Command、預期結果與說明文字全部混在一起。
最後產出的 Report,還得再清一次 UART 雜訊。
看起來每個問題都不太一樣。
但根因其實非常直接:
原始測試資料根本不是結構化的。
這些 Case 來自既有 Excel 與人工文件,本來就是寫給工程師看的。
一個儲存格裡,可能同時放著:
操作說明
+ 三條 Command
+ 預期結果
+ 前人留下來的貼心備註
人類看一眼,大概就知道要做什麼。
程式不行。
雖然有 Agent 協助把資料逐 Case YAML 化,但 415 個 Case 的工作量實在太大。
Agent 也會開始套用共通模式、補齊空白,或使用相似案例進行推論。
這些推論一旦被寫進 YAML,再交給 TestPilot 執行,就會從:
「可能是這樣。」
正式升級成:
「程式就是這樣跑。」
會偷懶的 Agent,寫出來的 TestPilot 也會偷懶。
最後程式只能努力猜:
哪一段是 Command?
到底有幾條?
這是要 Set,還是要 Read?
哪一段只是預期結果?
單獨執行幾條 Case 時,格式符合設計時的預期,看起來自然沒有問題。
但 415 條 Case 逐條跑下去,各種祖傳格式輪流登場,不同的測試流程互相混用,Parser 的運氣很快就會用完。
所以這次 PR 做的事情,說穿了就是替這批歷史資料加上一層翻譯:
從自然語言裡找出 Command。
把多條 Command 拆開並依序執行。
分辨這一步是操作,還是 Readback。
找出可能有問題的 ; 與 &&,先列出來人工確認。
不是 TestPilot 突然想把事情搞得很複雜。
是輸入本來就很複雜,只是以前都由工程師的大腦免費處理。
現在換成程式接手,這些純手工累積的技術債,終於寄來帳單了。
另一個很像的問題,是 Log 汙染。
但方向剛好相反。
原本設計給 Agent 使用的 serialwrap,為了辨認 UART 執行結果,會在 Log 中加入 BEGIN、RC 與 END Marker。
結果 TestPilot 還必須知道:
serialwrap 塞了哪些 Marker。
Return Code 藏在哪裡。
哪些 Prompt 應該清除。
如何把 Log 還原成人類可讀的樣子。
不過,這整件事是不是有哪裡怪怪的?
隱約有一種似曾相識的感覺。
硬幹多年後得到的普通 RD 沒用心得?
瘋狂處理例外邊界?
手工刻出來的 Excel 沒有結構,我只能先認命,替它補上一層翻譯。
但 serialwrap 是為 Agent 而生的。
怎麼連它也把 BEGIN、RC、END 全部丟給 TestPilot,再叫下一層從 Log 裡猜答案?
這不是在相容歷史問題。
這是在製造下一個歷史問題。
這個節奏不對。
Something is wrong!
「凡我所不欲,悉使 Agent。」
現在怎麼變成 TestPilot 在替 serialwrap 擦屁股?
下一篇,不再補 Regex,也不再多寫一層 Sanitizer。
我們直接把 serialwrap 拆掉重做,讓它自己對 Command、Output 與 Result 負責。
Have a nice day.
參考資料:
serialwrap
testtpilot-core