Files
STM32F4-Base/scripts/check_test_traceability.py
T
wandering1 029437be89 台架执行《实机测试实施指南》步骤 3~7 并闭环
步骤 3/4(IWDG V2/V4/V5 + 堆低水位 S11):
- V2 任务饿死 stall 后 ≤2.5s IWDG 复位;V4 关中断死循环 ~4.9s 稳定复位;
  V5 30min 120×FTP 全 PASS、零 IWDG 复位。堆告警全程未触发(min_ever=4728B)。
- task_watchdog.c:stall 证据写盘会经 nand_ftl_wd_feed 喂狗 → 增 s_stall_evidenced
  每次 stall 仅取一次证据,避免"取证据"本身阻止复位。
- default_task.c:堆低水位告警(HEAP_LOW_WATER_B=3072)。

步骤 5(GD5F 错误通道 S9):gd5f2gq5ue.c/.h + nand_ftl.c 错误码统一为 int32_t 并
全量检查返回值;套件回归 16/16 PASSED。
- test/run_all_suites.py:--forbidden 改为按套件覆盖(GD5F 用 "HardFault;assert")——
  TC-GD5F-704 故意触发 [ERR] 回落轮询,统一默认值会误判 FAIL。

步骤 6(网络稳定性):7.1 正常路径 ✅(V5 证据);7.2 N-2 丢弃可观测性代码已具备、
49s 静默在 V5 未复现(降级低风险观察项);7.3 N-3 待 PC peer。

步骤 7(工程卫生):
- S5:新增 test-cppcheck 钩子(仅 warning,排除模拟器桩)。
- S4:新增 scripts/check_test_traceability.py(默认仅告警)。
- 待核实项 TEST_SUITE_SELECTED 实机核实 = 死代码(无使用点);RTC 套件实际正常执行,
  真缺口 = 缺套件级汇总行 → sd2506_test_task.c 补 === RTC Tests: N/M PASSED === 并
  修正 Phase 级汇总累计问题;test_config.h 补齐 RTC 并注释"当前无引用"。

验证:产品 @build 0/0;pre-commit --all-files 17/17 Passed;
run_all_suites.py --suites RTC → PASS。
文档:实机测试实施指南 / SD2506_Test_Guide / 待处理内容 / 测试与架构改进建议 同步。
2026-10-04 00:35:06 +08:00

135 lines
5.3 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
# -*- coding: utf-8 -*-
"""check_test_traceability.py - S4 用例追溯校验(默认仅告警,不阻断提交)。
背景(docs/审查记录/测试与架构改进建议.md §3.5 / 实机测试实施指南.md §8.2):
文档声称 91 个 TC、代码有 222 个 TEST_CHECK(断言点数 > 用例数正常),
但**双向是否一一对应无人校验**——某个 TC 可能"文档写了、代码没实现",
或"代码实现了、文档没登记"。纯靠人肉比对。
本脚本做什么:
提取两侧的 TC 编号集合,输出双向差异:
① 代码侧:`test/board/*.c`(含 `*_test_task.c` 与 `net_latency_test.c`)
里出现的 `TC-<PFX>-<NNN>`(注释、DBG_INFO 串皆计入);
② 文档侧:`docs/测试说明/*_Test_Guide.md` 里出现的 `TC-<PFX>-<NNN>`。
[DOC-ONLY] 文档有、代码无 -> 未实现(或代码未标注)
[CODE-ONLY] 代码有、文档无 -> 未登记(或文档漏写)
作用域**只取 `*_Test_Guide.md`**:TC-CFG / TC-CLK / TC-WD 属主机侧单元
测试文档(`配置文件单元测试说明.md` 等),不参与板级追溯,天然被排除。
退出码:
0 = 无差异;或有差异但**默认仅告警**(与 doc-links 演进路径一致)
1 = 有差异且显式 `--strict`
2 = 环境错误(目录/文件缺失)
用法(仓库根目录):
& '.venv\\Scripts\\python.exe' scripts/check_test_traceability.py
& '.venv\\Scripts\\python.exe' scripts/check_test_traceability.py --strict
& '.venv\\Scripts\\python.exe' scripts/check_test_traceability.py --verbose
设计依据:docs/审查记录/测试与架构改进建议.md §3.5;docs/测试说明/实机测试实施指南.md §8.2
创建日期:2026-10-03
"""
import argparse
import re
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
CODE_DIR = REPO_ROOT / "test" / "board"
DOC_DIR = REPO_ROOT / "docs" / "测试说明"
# TC 编号:TC-<PFX>-<NNN>。<PFX> 允许字母数字(NET/GD5F/STO/RTC…)
TC_RE = re.compile(r"TC-[A-Za-z0-9]+-\d+")
# 占位符前缀(文档模板里的 `TC-XXX-…`)不参与比对
PLACEHOLDER_PREFIXES = {"XXX"}
def _prefix(tc: str) -> str:
return tc.rsplit("-", 1)[0]
def collect(paths, label):
"""返回 (ids_set, per_prefix_counts)。逐文件扫描,缺文件不致命。"""
ids = set()
for p in paths:
try:
text = p.read_text(encoding="utf-8", errors="replace")
except OSError as e: # pragma: no cover - 环境错误
print(f"[ERR] 无法读取 {p}: {e}", file=sys.stderr)
return None, None
for m in TC_RE.findall(text):
if _prefix(m) in PLACEHOLDER_PREFIXES:
continue
ids.add(m)
counts = {}
for i in sorted(ids):
counts[_prefix(i)] = counts.get(_prefix(i), 0) + 1
return ids, counts
def main():
ap = argparse.ArgumentParser(description="S4 用例追溯校验(TC 编号双向比对)")
ap.add_argument("--strict", action="store_true",
help="有差异时返回退出码 1(默认仅告警,返回 0)")
ap.add_argument("--verbose", action="store_true",
help="额外打印各前缀的 TC 计数")
ap.add_argument("--code-dir", default=str(CODE_DIR))
ap.add_argument("--doc-dir", default=str(DOC_DIR))
args = ap.parse_args()
code_dir = Path(args.code_dir)
doc_dir = Path(args.doc_dir)
code_files = sorted(code_dir.glob("*.c")) if code_dir.is_dir() else []
doc_files = sorted(doc_dir.glob("*_Test_Guide.md")) if doc_dir.is_dir() else []
if not code_files:
print(f"[ERR] 未找到代码侧文件:{code_dir}/*.c", file=sys.stderr)
return 2
if not doc_files:
print(f"[ERR] 未找到文档侧文件:{doc_dir}/*_Test_Guide.md", file=sys.stderr)
return 2
code_ids, code_counts = collect(code_files, "code")
doc_ids, doc_counts = collect(doc_files, "doc")
if code_ids is None or doc_ids is None:
return 2
doc_only = sorted(doc_ids - code_ids)
code_only = sorted(code_ids - doc_ids)
print("=== S4 用例追溯校验(TC 编号双向比对)===")
print(f"代码侧:{len(code_files)} 个文件,{len(code_ids)} 个 TC")
print(f"文档侧:{len(doc_files)} 个文件,{len(doc_ids)} 个 TC")
print(f"交集:{len(code_ids & doc_ids)}")
if args.verbose:
print("--- 各前缀 TC 计数(代码 / 文档)---")
for pfx in sorted(set(code_counts) | set(doc_counts)):
print(f" {pfx}: code={code_counts.get(pfx, 0)} doc={doc_counts.get(pfx, 0)}")
print()
print(f"[DOC-ONLY] 文档有、代码无(未实现/代码未标注):{len(doc_only)}")
for i in doc_only:
print(f" {i}")
print(f"[CODE-ONLY] 代码有、文档无(未登记/文档漏写):{len(code_only)}")
for i in code_only:
print(f" {i}")
if not doc_only and not code_only:
print("\n[OK] 双向一一对应,无差异")
return 0
print(f"\n[WARN] 共 {len(doc_only) + len(code_only)} 处差异"
f"(默认仅告警,不阻断提交;收敛后再考虑升为门禁)")
return 1 if args.strict else 0
if __name__ == "__main__":
sys.exit(main())