Files
STM32F4-Base/test/verify_log.py
T

135 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""verify_log.py - 对抓取的 MCU 调试日志做标记校验(纯标准库,2026-09-20 替代 verify_log.ps1)。
用于 @test 闭环:烧录后从串口助手日志提取的开机日志交给本脚本 grep 标记,
输出结构化判定(格式与旧 PowerShell 版逐字一致,下游文档/习惯不受影响)。
用法:
& '.venv\\Scripts\\python.exe' test/verify_log.py --log-path <file>
[--required 'marker1;marker2']
[--forbidden 'FAIL;ERROR;HardFault;assert;[ERR]']
[--show-tail-lines 40]
退出码(与旧版一致):
0 = PASS(所有 Required 标记找到,Forbidden 零命中)
1 = FAIL(缺 Required 或命中 Forbidden)
2 = ERROR(日志文件不存在/不可读)
默认 Required 标记对应 STM32F4-Base 启动序列(见 App/app_main.c + App/task/net_task.c),
按测试用例覆盖,例如:
--required 'App init OK;net init: OK;PHY: 100M FULL'
--forbidden 'FAIL;ERROR;HardFault;assert;[ERR]'
语义约定(与旧 PowerShell 版对齐):
- Required:大小写敏感子串匹配(全文)
- Forbidden:大小写**不敏感**子串匹配、逐行计数(旧版 -like 语义;
如 Forbidden 'FAIL' 会命中 "write /log/run.log fail (7)" 这类行)
- 固件错误级日志前缀是 `[ERR]`(`DBG_ERROR`,形如 `[时间][ERR][TAG] 消息`),
与 `ERROR` 子串**不匹配**——默认 Forbidden 因此显式包含 `[ERR]`,驱动/模块
错误行不再漏检(2026-09-21 修复)。测试套件中**预期**出现 `[ERR]` 的用例
(如 GD5F DMA 守卫负路径、FatFs 非法名校验)须用 --forbidden 覆盖默认值。
- 读取编码:先按 UTF-8,解码失败回退系统 locale 编码(旧版 Get-Content 行为)
- 控制台输出重配置为 UTF-8(errors=replace):任何 Windows 控制台代码页
都只影响终端显示,不会导致本脚本崩溃
"""
import argparse
import locale
import sys
from pathlib import Path
REQUIRED_DEFAULT = "App init OK;Network Init;net init: OK"
FORBIDDEN_DEFAULT = "FAIL;ERROR;HardFault;assert;[ERR]"
def _split_list(spec):
"""';' 分隔列表 → 去空白、去空项。"""
return [p.strip() for p in spec.split(";") if p.strip()]
def _read_lines(path):
raw = Path(path).read_bytes()
for enc in ("utf-8", locale.getpreferredencoding(False)):
try:
return raw.decode(enc).splitlines()
except UnicodeDecodeError:
continue
return raw.decode("utf-8", errors="replace").splitlines()
def verify(log_path, required=REQUIRED_DEFAULT, forbidden=FORBIDDEN_DEFAULT,
show_tail=40, out=print):
"""标记校验核心(可被 run_boot_test.py 复用)。返回退出码 0/1/2。"""
p = Path(log_path)
if not p.is_file():
out("VERIFY_ERROR: log file not found: {}".format(log_path))
return 2
try:
lines = _read_lines(log_path)
except OSError:
out("VERIFY_ERROR: cannot read log file: {}".format(log_path))
return 2
full = "\n".join(lines)
req_list = _split_list(required)
forb_list = _split_list(forbidden)
out("=== Log verdict: {} ===".format(log_path))
out("Lines: {}, bytes: {}".format(len(lines), p.stat().st_size))
missing = []
for m in req_list:
if m in full:
out("[OK] required marker found: {}".format(m))
else:
out("[MISS] required marker NOT found: {}".format(m))
missing.append(m)
hits = []
for f in forb_list:
fl = f.lower()
n = sum(1 for ln in lines if fl in ln.lower())
if n > 0:
out("[HIT] forbidden pattern: {} ({} line(s))".format(f, n))
hits.append(f)
else:
out("[OK] forbidden pattern absent: {}".format(f))
if show_tail > 0 and lines:
out("")
out("=== Last {} line(s) of log ===".format(show_tail))
for ln in lines[-show_tail:]:
out(ln)
out("")
if missing or hits:
out("VERDICT: FAIL")
return 1
out("VERDICT: PASS")
return 0
def main(argv=None):
# 控制台输出固定 UTF-8:Windows 代码页(GBK/UTF-8 终端)均不会让本脚本崩溃
for stream in (sys.stdout, sys.stderr):
try:
stream.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
ap = argparse.ArgumentParser(description="MCU debug log marker check (pure stdlib)")
ap.add_argument("--log-path", required=True, help="boot log file to check")
ap.add_argument("--required", default=REQUIRED_DEFAULT,
help="required markers, ';' separated (case-sensitive)")
ap.add_argument("--forbidden", default=FORBIDDEN_DEFAULT,
help="forbidden patterns, ';' separated (case-insensitive)")
ap.add_argument("--show-tail-lines", type=int, default=40,
help="show last N log lines (0 = off)")
args = ap.parse_args(argv)
return verify(args.log_path, args.required, args.forbidden, args.show_tail_lines)
if __name__ == "__main__":
sys.exit(main())