批2(QA harness·B覆盖总览面板+本体枚举器): 义务层=tools/extract_capability_list.py(README能力清单四列表按旅程段解析,7段103行,用例指针剥反引号); 执行层=tools/coverage_receipts.py六源回执明细化(冒烟/单元集成/系统/探针/渲染/静态各一.js,名×结果×计数粒度,日志缺失=not_run亮灰不冒充绿)+run_all_gates.sh末尾挂点(观测件非判定件,解析失败打红字不翻闸判)+probe_manifest.js(面板file://无法列目录,按现存登记文件落清单); 机器义务双源之本体源=tools/coverage_enum.py(两扫描器+比对:.tres行集/schema词表字段/tscn按钮/gui_input自绘类/屏模态清单×extract_checks断言文料,865实体/缺口212); 呈现层=tools/coverage_report.html(四节:六层闸况/能力行×六层灯按旅程段/控件下钻交互源+渲染golden展示源/本体对账缺口×探针命中降级探针承载;状态灯四态+数据时间戳陈旧可见+完备性边界页眉;JSONP直载file://即开); 豁免=tools/coverage_exemptions.js人工维护母版; 登记件扩seen面(场景树全量BaseButton+db布局表hotspot_key公共面枚举,seen-hit=存在但零真输入覆盖缺口数据源)。读数: 两探针窄跑绿(screen_flow 19命中/116存在,ui_click 27命中/91存在——停手钮/树购买重建代等真缺口在册,弃图刷新确认链走视图方法属衔接条目余项); 六源明细化对真实闸日志抽核(unit82套件/系统69检查/探针10段/静态13条/golden逐屏L1-L3); 面板浏览器真实入口四节渲染验过; 全量档闸【验收通过】无新增红(exit=0,回执commit 07d4977f,六源明细化在闸内实跑=挂点真路径验证)
This commit is contained in:
1 parent
07d4977f41
commit
bb9ce8a117
7 files changed
+745
No files matched your search
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""extract_capability_list.py — 能力清单义务层提取器(B 覆盖总览面板·义务层)
|
||||
|
||||
把 game/tests/README.md「能力清单」的四列表(能力|系统|真实入口|代表用例)按旅程段
|
||||
解析成机器可读 JSONP .js,供 tools/coverage_report.html 经 <script src> 直载
|
||||
(file:// 下 fetch 本地 json 被 CORS 拦——JSONP 是 B 面板全数据源的统一形态)。
|
||||
|
||||
用法:
|
||||
python tools/extract_capability_list.py [--readme <路径>] [--out <路径>]
|
||||
|
||||
判据口径:
|
||||
- 只认表头恰为「能力|系统|真实入口|代表用例」的四列表(覆盖域表等五列表不取);
|
||||
- 旅程段标题形态=「**旅程段N:名称**」,段名进每行的 segment 字段;
|
||||
- 代表用例列按「;」切分成用例指针数组(`文件:函数` 形态逐字保真);
|
||||
- 本工具是义务层的静态投影,产出随 README 变更重跑即得——面板只读本产物,禁在面板里改义务。
|
||||
|
||||
产出(JSONP):
|
||||
window.__QA_COVERAGE__.capability_list = {
|
||||
"meta": {"generated_at", "source", "rows_total", "segments_total"},
|
||||
"segments": [{"segment": 名称, "rows": [{"ability", "system", "entry", "cases": []}]}],
|
||||
}
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
DEFAULT_README = ROOT / "game" / "tests" / "README.md"
|
||||
DEFAULT_OUT = ROOT / "game" / "tests" / ".gates" / "coverage" / "capability_list.js"
|
||||
|
||||
SEGMENT_RE = re.compile(r"^\*\*旅程段[一二三四五六七八九十]+[::](.+?)\*\*\s*$")
|
||||
|
||||
|
||||
|
||||
def split_row(line: str) -> list[str]:
|
||||
body = line.strip()
|
||||
if body.startswith("|"):
|
||||
body = body[1:]
|
||||
if body.endswith("|"):
|
||||
body = body[:-1]
|
||||
return [c.strip() for c in body.split("|")]
|
||||
|
||||
|
||||
def parse(readme: Path) -> dict:
|
||||
lines = readme.read_text(encoding="utf-8").splitlines()
|
||||
segments: list[dict] = []
|
||||
cur_seg: str | None = None
|
||||
cur_rows: list[dict] | None = None
|
||||
in_capability_table = False
|
||||
for line in lines:
|
||||
seg = SEGMENT_RE.match(line.strip())
|
||||
if seg:
|
||||
if cur_seg is not None:
|
||||
segments.append({"segment": cur_seg, "rows": cur_rows or []})
|
||||
cur_seg = seg.group(1).strip()
|
||||
cur_rows = []
|
||||
in_capability_table = False
|
||||
continue
|
||||
if cur_seg is None:
|
||||
continue
|
||||
if not line.strip().startswith("|"):
|
||||
in_capability_table = False
|
||||
continue
|
||||
cells = split_row(line)
|
||||
if all(not set(c) - {"-", " ", ":"} for c in cells) and cells:
|
||||
continue # 表头分隔行 |---|---|
|
||||
if (len(cells) == 4 and cells[0] == "能力" and cells[1] == "系统"
|
||||
and cells[2] == "真实入口" and cells[3].startswith("代表用例")):
|
||||
in_capability_table = True
|
||||
continue
|
||||
if in_capability_table and len(cells) == 4:
|
||||
ability, system, entry, cases = cells
|
||||
cur_rows.append({
|
||||
"ability": ability.strip("`"),
|
||||
"system": system,
|
||||
"entry": entry,
|
||||
"cases": [c.strip().strip("`") for c in cases.split(";") if c.strip()],
|
||||
})
|
||||
if cur_seg is not None:
|
||||
segments.append({"segment": cur_seg, "rows": cur_rows or []})
|
||||
rows_total = sum(len(s["rows"]) for s in segments)
|
||||
return {
|
||||
"meta": {
|
||||
"generated_at": datetime.datetime.now().isoformat(timespec="seconds"),
|
||||
"source": str(readme),
|
||||
"rows_total": rows_total,
|
||||
"segments_total": len(segments),
|
||||
},
|
||||
"segments": segments,
|
||||
}
|
||||
|
||||
|
||||
def dump_js(payload: dict, out: Path) -> None:
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
js = "/* 能力清单义务层投影——由 extract_capability_list.py 生成,随 README 变更重跑;人工修订无效(义务源=README 能力清单)。 */\n"
|
||||
js += "window.__QA_COVERAGE__ = window.__QA_COVERAGE__ || {};\n"
|
||||
js += "window.__QA_COVERAGE__.capability_list = %s;\n" % json.dumps(payload, ensure_ascii=False, indent=2)
|
||||
out.write_text(js, encoding="utf-8", newline="\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if hasattr(sys.stdout, "reconfigure"):
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
ap = argparse.ArgumentParser(description="能力清单义务层提取(B 面板·义务层)")
|
||||
ap.add_argument("--readme", default=str(DEFAULT_README))
|
||||
ap.add_argument("--out", default=str(DEFAULT_OUT))
|
||||
args = ap.parse_args()
|
||||
readme = Path(args.readme)
|
||||
if not readme.is_file():
|
||||
print("[capability] README 不存在:%s" % readme)
|
||||
return 1
|
||||
payload = parse(readme)
|
||||
dump_js(payload, Path(args.out))
|
||||
print("[capability] 义务层投影已落盘 %s(旅程段 %d / 能力行 %d)" % (
|
||||
args.out, payload["meta"]["segments_total"], payload["meta"]["rows_total"]))
|
||||
if payload["meta"]["rows_total"] == 0:
|
||||
print("[capability] 红:能力行数为 0——表头或旅程段标题形态不匹配,禁当空义务面用")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in new issue
Block a user