iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0
AI Engineering

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

Day 4 - 測試都自動化了,還在人工剪 Log:UART 資料為什麼要拆成三層

  • 分享至 

  • xImage
  •  

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

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

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

Today’s Change

  • Change Ref: PR #7-feat: serialwrap RAW log integration,第一階段 Commit 07129e

  • Issue: TestPilot 已經可以自動執行 Test Case、產生 Verdict 與 Report,但每條 Case 對應的 DUT/STA UART Log 區間,仍然需要人工整理。

  • Root Cause: 原始 UART 資料、清理後的 Command Result 與最終測試判定,被混在同一份 Terminal Transcript 裡;人類可以忽略其中的控制字元與畫面效果,程式卻無法穩定地靠文字 Offset、行號或 Regex 建立可追溯關係。

  • Solution: 將 UART 資料拆成 RAW、Result、Decision 三層;TestPilot 在每條 Case 執行前後記錄原始 UART Record 的 Sequence,再產生 DUT/STA 可讀 Log 與逐 Case Line Reference。

  • Evidence: 完成 D009/D014 兩條 Test Case 的實機 Run;產生 15 KB/464 行 DUT Log、6.5 KB/247 行 STA Log 與逐 Case Log Reference;新增 32 個 Unit Tests,Full Suite 共 1513 Tests Pass。


上一篇說要記帳,今天真的開始記

上一篇最後,我們把 Command、Output 與 Result 的責任還給 serialwrap。

TestPilot 不需要再理解 BEGIN/END Marker,也不用替 serialwrap 清 Prompt、找 RC、猜 Command 到底跑完了沒有。

它只要提交 Command,再拿回結構化 Result。

看起來,這次總算可以專心跑測試了。

但當時的 TestPilot 不只是拿來執行幾條 Command。

我正在用它校正四百多條 LLAPI Test Case。

每一條 Case 都不是跑完、看到 PASS,然後雙手一攤:

好,下一條。

真正的校正流程還要確認:

  • Workbook 裡原本寫的預期是否正確。

  • 待測設備 DUT 的 Northbound API 有沒有真的接受設定。

  • Getter、Runtime Config 與 Driver 狀態是否一致。

  • 陪測設備 STA 有沒有在正確的 Band 上完成連線。

  • Verdict 到底是 Pass、Fail、Not Supported,還是只是目前測不到。

  • 當時 DUT 與 STA 的 UART 上,究竟發生了什麼。

所以每做完一條 Case,我還要把:

Case ID
Workbook Row
Command
Verdict
DUT Log 區間
STA Log 區間
判定用的 Log 摘錄

整理進 Audit Report。

一條一條校正時,這件事還做得下去。

我打開 minicom Log,找到剛才的操作,複製幾段 Output,再把行號填回 Report。

等到 TestPilot 開始可以自動執行整批 Case、自己產生 Markdown 與 JSON Report 後,事情突然變得有點奇怪。

Agent 像保姆一樣地護著 TestPilot 跑測試。

TestPilot 負責依法判刑。

我負責在兩份 minicom Log 裡比對證據,所以我是書記官?

欸,等等,這不對,為什麼又是我受傷害?

自動化非常精準地把最無聊的部分保留給了我!!!

FullRun


最直覺的解法:讓程式幫我記行號

既然人工流程是:

Case 開始
→ 看一下 DUT/STA Log 現在到哪裡
→ 執行 Case
→ 再看一次 Log
→ 把中間那段填進 Report

那就讓 TestPilot 自己做。

Case 開始前,記住 minicom Log 的 Offset 或行號。

Case 結束後,再記一次。

中間那一段,不就是這條 Case 的 UART 證據嗎?

這個方法不用改 serialwrap,也不用新增什麼複雜 Protocol。

TestPilot 本來就知道 Case 何時開始、何時結束。

minicom 也已經有 -C Log。

兩邊接起來,看起來就結束了。

嗯嗯……這套做法看起來非常眼熟,好像在哪見過?

Day 1 的 serialwrap,正是靠記住 minicom Log Offset,再從持續成長的檔案裡切出 Command Output。

先把 Terminal Log 當成資料來源,再從裡面找出我要的部分。

現在只是把 Command Boundary 放大成 Case Boundary。

同一套思路。

規模升級。

看起來也很合理。

然後我開始認真看那份 Log。


畫面看起來正常,不代表檔案真的只是文字

minicom 畫面上的 Command、Output 與 Prompt 都很正常,人工核對也沒有問題。

但寫進 Log 的內容,可能還夾著 ANSI Escape Sequence、CR、Backspace、BEL 或 Bracketed Paste Code。

Terminal 會替人類處理這些顯示效果,程式拿到的卻只是原始 Byte。

所以同一份 Log,人工找行號可以,程式拿來算 Offset、切行或跑 Regex,就可能因為多一個控制字元而失準。

後來 serialwrap 的 file pull 也真的踩過同一類問題:畫面看起來沒什麼異常,Base64 中間卻混入 Terminal Control Code,最後直接 BASE64_DECODE_FAILED

這不是再多補一支 Sanitizer 就能完全解決。

因為我要保存原始資料時,不能先把控制字元清掉;要讓程式解析時,卻又必須清理它們。

同一份 Log,同時被要求忠於現場、方便閱讀,最後還要負責 Pass/Fail。

我等於把三種互相衝突的責任,全塞回同一個檔案。

我好難啊~


一份 UART Log,做不了三份工作

把需求拆開後,其實只有三件事:

RAW
→ 當時真正經過 UART 的資料是什麼?

Result
→ 這條 Command 整理後得到什麼答案?

Decision
→ 目前的證據足不足以支持 Verdict?

新的 serialwrapd 已經直接持有實體 UART,所有經過 Broker 的 RX/TX 都會被逐筆記錄,包含 Sequence、COM、方向、來源、Command ID 與完整性資訊。

這份不做顯示清理的 UART 流水帳,就是 WAL。

RAW Layer 不需要理解畫面,只負責把現場留下來。

Result Layer 再依 Command Lifecycle 整理 Output,處理 Prompt、Command Echo 與 Terminal Noise,回傳 TestPilot 真正能使用的結構化 Result。

最後才由 TestPilot 的 pass_criteria 產生 Decision。

這樣即使 Result 看起來可以解析,原始紀錄若已經標記遺失或截斷,Decision Layer 也不應該裝作沒看到。

D2 已經證明三條 Command 只跑一條也能 PASS。

我不想再把它進化成:

三段資料只收到一段,也能 PASS。

FullRun

TestPilot 只需要在帳本裡插書籤

serialwrap 知道每筆 UART Record 的 Sequence。

TestPilot 則知道一條 Case 何時進入 execute_with_retry(),又在什麼時候離開。

所以不需要再把 Case ID 寫進 DUT,也不用重新發明另一組 BEGIN/END Marker。

只要在 Case 前後各記一次目前的 Sequence:

seq_before
→ execute_with_retry()
→ seq_after

這段 Sequence Range 就是該 Case 對應的原始 UART 區間。

Run 結束後,再依 COM 把 Record 分成 DUT/STA,解碼成可閱讀的 Log,並建立 Sequence 到行號的 Mapping。

程式用 Sequence 綁定原始紀錄;Report 裡的 Line Reference 則讓我可以直接跳到對應位置查看。

這次先用 D009 與 D014 兩條 LLAPI Test Case 做實機驗證,最後產生:

DUT.log:15 KB,464 行
STA.log:6.5 KB,247 行

Markdown Report 開始帶有逐 Case 的 DUT/STA Log Reference,JSON Result 也加入:

dut_log_lines
sta_log_lines

另外新增 32 個 Log Capture Unit Tests,完整 Test Suite 共 1513 Tests Pass。

TestPilot 從這裡開始,不再只留下:

我判斷這條 Case 是 PASS。

它還能指回當時的 UART 原始紀錄,讓人重新檢查這個結論是怎麼來的。

看來我離成功當薪水小偷的日子,又更靠近了一些……(揮汗)

FullRun

為了清帳,重開 serialwrap,也順便讓 minicom 葛屁了

既然每次 Run 都要記錄自己的 Sequence Range,第一版當然希望從一份乾淨的 WAL 開始。

於是流程做得很直接:

Stop daemon
→ Clean WAL
→ Start daemon
→ Bind sessions
→ Run cases
→ Export logs
→ Stop daemon

舊資料不會混進來,Sequence 每次也都從頭開始。

我一跑 TestPilot,旁邊正在看的 minicom 就斷線了。

欸!!!重新打開再跑一次。

喂~~又斷!!!

見鬼,又有 Bug 了喔?

嘶~~~喔喔喔喔~我想到了!!!

現在 minicom 連的是 serialwrapd 提供的 PTY。

我只是想清掉這次 Test Run 的帳,卻順便把負責 UART、Session 與 Human Console 的 daemon 一起重啟了。

資料被拆成 RAW、Result 與 Decision 三層之後,下一個混在一起的東西也出現了:

重設這次 Run 的資料
≠
重設整套 UART Runtime

下一篇,清帳歸清帳,不要再拉著全家人一起連坐。

Have a nice day.


上一篇
Day 3 - 為了知道 Command 跑完沒,我們先把LOG搞髒了:UART LOG 汙染
系列文
一天一個 PR,讓我告訴你嵌入式開發的殘酷:如何為 AI Agent 建立可信的工程閉環4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言