Files
gplan-2/tools/extract_capability_list.py
T
DKChan bb9ce8a117 批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,六源明细化在闸内实跑=挂点真路径验证)
2026-11-06 03:21:10 +08:00

130 lines
5.2 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 -*-
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())