029437be89
步骤 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 / 待处理内容 / 测试与架构改进建议 同步。
135 lines
5.3 KiB
Python
135 lines
5.3 KiB
Python
#!/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())
|