Files
gplan-2/tools/coverage_report.html
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

227 lines
14 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>QA 覆盖总览面板——Dungeon Sweeper</title>
<style>
:root { --ok:#3a7d44; --bad:#a33; --off:#9aa; --na:#d5d5d0; --ex:#b8860b; --ink:#2c2a26; --paper:#f4f1ea; }
body { font-family: "Microsoft YaHei", sans-serif; margin: 0; background: var(--paper); color: var(--ink); }
header { padding: 14px 20px 10px; border-bottom: 3px solid var(--ink); background: #fff; }
h1 { font-size: 19px; margin: 0 0 4px; }
h2 { font-size: 16px; margin: 22px 0 6px; border-left: 5px solid var(--ink); padding-left: 8px; }
.note { font-size: 12px; color: #555; margin: 3px 0; }
.warn { color: var(--bad); font-weight: bold; }
main { padding: 12px 20px 40px; }
table { border-collapse: collapse; width: 100%; background: #fff; font-size: 12.5px; }
th, td { border: 1px solid #cfc9bd; padding: 4px 7px; text-align: left; vertical-align: top; }
th { background: #ebe6da; }
.lamp { display: inline-block; width: 13px; height: 13px; border-radius: 50%; margin-right: 3px; vertical-align: -2px; }
.ok { background: var(--ok); } .bad { background: var(--bad); }
.off { background: var(--off); } .na { background: var(--na); } .ex { background: #fff; border: 3px double var(--ex); }
.chip { display: inline-block; padding: 1px 8px; border-radius: 9px; color: #fff; font-size: 12px; }
details { margin: 4px 0 10px; } summary { cursor: pointer; font-size: 13px; }
.gaplist { font-size: 12px; color: #444; line-height: 1.7; }
.seg { margin-bottom: 18px; }
code { background: #eceade; padding: 0 3px; }
</style>
</head>
<body>
<header>
<h1>QA 覆盖总览面板 <span style="font-weight:normal;font-size:13px">(义务 × 执行合并视图——只读)</span></h1>
<div class="note">状态灯:绿=该层最近跑批过 红=跑批挂 灰=该层无承载/未跑 双圈=豁免(<code>tools/coverage_exemptions.js</code> 人工维护)。</div>
<div class="note" id="staleness"></div>
<div class="note"><b>完备性边界</b>:控件级视图只对「被探针/C 打开过的屏」负责;未打开过的屏由动线断言+C 探索兜底。
本体对账为文本 grep 关联——有假阳假阴(断言测了实体但文本没带 id=假缺口,人工核销;文本带 id 但断言弱=假覆盖),缺口须人工复核后再立事项。</div>
<div class="note">数据源:随最近一次闸批落盘于 <code>game/tests/.gates/coverage/</code>;刷新本页即读最新。</div>
</header>
<main>
<h2>一、六层闸况</h2>
<div id="gateSummary"></div>
<h2>二、能力清单 × 六层状态灯(按旅程段)</h2>
<div class="note">灯的判据:用例文件落哪层就钉哪层的回执(单元=逐套件 PASS/FAIL;探针/系统/冒烟/渲染/静态=层判据);该层无对应承载=灰。</div>
<div id="capabilityMatrix"></div>
<h2>三、控件级交互覆盖下钻(探针真输入登记)</h2>
<div class="note">交互源=探针真输入命中登记(push_input 管线命中);「存在但零命中」=控件缺口——hitbox 错位/遮挡/驱动空洞在此暴露。展示源=渲染闸 golden 逐屏登记。</div>
<div id="controlDrill"></div>
<h2>四、本体对账(游戏内容 ↔ 测试验证)</h2>
<div class="note">缺口=本体存在、断言文料零关联;若该 id 出现在探针命中登记则降级为「探针承载」。首见批次=本工具生成时间,逐批对比看新面孔。</div>
<div id="ontologyGaps"></div>
</main>
<script src="./coverage_exemptions.js"></script>
<script src="../game/tests/.gates/coverage/capability_list.js"></script>
<script src="../game/tests/.gates/coverage/gate_smoke.js"></script>
<script src="../game/tests/.gates/coverage/gate_unit.js"></script>
<script src="../game/tests/.gates/coverage/gate_system.js"></script>
<script src="../game/tests/.gates/coverage/gate_probe.js"></script>
<script src="../game/tests/.gates/coverage/gate_render.js"></script>
<script src="../game/tests/.gates/coverage/gate_static.js"></script>
<script src="../game/tests/.gates/coverage/probe_manifest.js"></script>
<script src="../game/tests/.gates/coverage/ontology_gaps.js"></script>
<script>
/* 探针登记文件由 manifest 同步补挂(document.write 在解析期阻塞加载,渲染段前数据已就位) */
(function () {
const probes = ((window.__QA_COVERAGE__ || {}).probe_manifest || {}).probes || [];
for (const p of probes) {
document.write('<script src="../game/tests/.gates/coverage/' + encodeURIComponent(p) + '"><\/script>');
}
})();
</script>
<script>
"use strict";
const C = window.__QA_COVERAGE__ || {};
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"]/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c])); }
function lamp(cls, title) { return `<span class="lamp ${cls}" title="${esc(title)}"></span>`; }
function chip(txt, cls) { return `<span class="chip" style="background:${cls}">${esc(txt)}</span>`; }
const VCOLOR = {"绿":"var(--ok)","红":"var(--bad)","灰":"var(--off)","not_run":"var(--off)"};
// ── 数据新鲜度(陈旧可见——防静默旧数据) ──
(function () {
const stamps = [];
const cl = (C.capability_list || {}).meta; if (cl) stamps.push(`义务层 ${cl.generated_at}`);
const rc = C.gate_receipts || {};
for (const k in rc) { const m = (rc[k].receipt || {}).meta; if (m) stamps.push(`${m.label} ${m.generated_at}`); }
const on = (C.ontology || {}).meta; if (on) stamps.push(`本体 ${on.generated_at}`);
const pm = (C.probe_manifest || {}).meta; if (pm) stamps.push(`探针登记 ${pm.generated_at}`);
document.getElementById("staleness").innerHTML = "数据时间:" + stamps.map(esc).join(" · ");
})();
// ── 一、六层闸况 ──
(function () {
const rc = C.gate_receipts || {};
const ORDER = ["smoke", "unit", "system", "probe", "render", "static"];
const LABEL = {smoke:"冒烟测试", unit:"单元与集成测试", system:"系统测试", probe:"场景探针", render:"渲染闸", static:"静态扫描"};
let html = "<table><tr><th>层</th><th>判定</th><th>明细</th></tr>";
for (const k of ORDER) {
const r = (rc[k] || {}).receipt;
if (!r) { html += `<tr><td>${LABEL[k]}</td><td>${chip("缺数据", "var(--off)")}</td><td>—</td></tr>`; continue; }
const v = r.result === "not_run" ? "未跑" : r.result;
const color = VCOLOR[r.result] || "var(--off)";
let detail = `共 ${r.items ? r.items.length : 0} 项`;
if (r.checks != null) detail = `${r.checks} 项检查 / ${r.fails} 失败`;
if (r.suites_total != null) detail = `${r.suites_total} 套件`;
if (r.probes_total != null) detail = `${r.probes_total} 探针`;
html += `<tr><td>${LABEL[k]}</td><td>${chip(v, color)}</td><td>${esc(detail)} <span class="note">源=${esc(r.meta.source_log)} @ ${esc(r.meta.generated_at)}</span></td></tr>`;
}
html += "</table><details><summary>单元与集成逐套件明细</summary><div class='gaplist'>";
const unit = ((rc.unit || {}).receipt || {}).items || [];
for (const it of unit) {
html += `${lamp(it.result === "PASS" ? "ok" : "bad", it.result)} ${esc(it.name.replace("res://tests/", ""))}${it.exit ? "(exit=" + esc(it.exit) + ")" : ""}<br>`;
}
html += "</div></details><details><summary>场景探针逐段明细</summary><div class='gaplist'>";
const pr = ((rc.probe || {}).receipt || {}).items || [];
for (const it of pr) {
html += `${lamp(it.result === "PASS" ? "ok" : (it.result === "FAIL" ? "bad" : "off"), it.result)} ${esc(it.name)}${it.segment ? "(" + esc(it.segment) + ")" : ""}<br>`;
}
html += "</div></details><details><summary>静态扫描逐条明细</summary><div class='gaplist'>";
const st = ((rc.static || {}).receipt || {}).items || [];
for (const it of st) {
html += `${lamp(it.result === "ok" ? "ok" : "bad", it.result)} ${esc(it.name)}<br>`;
}
html += "</div></details>";
document.getElementById("gateSummary").innerHTML = html;
})();
// ── 二、能力清单 × 六层灯 ──
(function () {
const cl = C.capability_list;
if (!cl || !cl.segments) { document.getElementById("capabilityMatrix").innerHTML = "<p class='warn'>义务层缺数据——先跑 tools/extract_capability_list.py</p>"; return; }
const rc = C.gate_receipts || {};
const ex = ((C.exemptions || {}).abilities || []).map(s => String(s));
function layerOf(caseFile) {
if (/system_test/.test(caseFile)) return "system";
if (/probe\//.test(caseFile) || /_probe\.gd/.test(caseFile)) return "probe";
if (/golden|render/.test(caseFile)) return "render";
return "unit";
}
function rowLight(row) {
if (ex.includes(row.ability)) return lamp("ex", "豁免(coverage_exemptions.js 在册)");
if (!row.cases || !row.cases.length) return lamp("off", "无代表用例指针");
let worst = null; const seenLayers = new Set();
for (const c of row.cases) {
const layer = layerOf(c);
seenLayers.add(layer);
const r = (rc[layer] || {}).receipt;
if (!r || r.result === "not_run") { worst = worst || "na"; continue; }
if (r.result === "红") return lamp("bad", `${layer} 层跑批挂`);
if (layer === "unit") {
const items = r.items || [];
const hit = items.filter(it => c.indexOf(it.name.replace("res://tests/", "")) >= 0 || it.name.indexOf(c.split(":")[0].replace(".gd", "")) >= 0);
if (!hit.length) { worst = worst || "off"; continue; }
if (hit.some(it => it.result !== "PASS")) return lamp("bad", "代表套件红:" + c);
worst = worst || "ok";
} else {
worst = worst || (r.result === "绿" ? "ok" : "off");
}
}
if (worst === "ok") return lamp("ok", "承载层绿");
if (worst === "na") return lamp("na", "承载层本批未跑");
return lamp("off", "该层无对应承载(灰)");
}
let html = "";
for (const seg of cl.segments) {
if (!seg.rows.length) continue;
html += `<div class="seg"><h3 style="font-size:14px;margin:8px 0 4px">${esc(seg.segment)}(${seg.rows.length} 行)</h3>`;
html += "<table><tr><th style='width:32%'>能力</th><th>系统</th><th style='width:14%'>灯</th><th style='width:26%'>代表用例</th></tr>";
for (const row of seg.rows) {
html += `<tr><td>${esc(row.ability)}</td><td>${esc(row.system)}</td><td>${rowLight(row)}</td><td>${row.cases.map(esc).join("<br>")}</td></tr>`;
}
html += "</table></div>";
}
document.getElementById("capabilityMatrix").innerHTML = html;
})();
// ── 三、控件级下钻 ──
(function () {
const pm = C.probe_manifest || {probes: []};
const ex = ((C.exemptions || {}).controls || []).map(s => String(s));
const rd = ((C.gate_receipts || {}).render || {}).receipt || {};
const renderScreens = [];
for (const it of (rd.items || [])) if (it.screens instanceof Array) renderScreens.push(...it.screens);
let html = "";
if (renderScreens.length) {
html += `<p class="note"><b>渲染展示登记(golden 逐屏)</b>:${renderScreens.map(esc).join("、")}</p>`;
}
for (const pf of pm.probes) {
const name = pf.replace(/^probe_hits_/, "").replace(/\.js$/, "");
// 同名重复 <script> 无法动态注入——manifest 落盘时同步内联一份数据入口由面板经 window 取
const data = (window.__QA_COVERAGE__.probe_real_input || {})[name];
if (!data) { html += `<p class="warn">${esc(name)}:登记文件未加载(检查 script 标签与 manifest)</p>`; continue; }
const hits = data.hits || {};
const seen = data.seen || [];
const gaps = seen.filter(k => !(k in hits) && !ex.some(e => k.indexOf(e) >= 0));
html += `<details open><summary><b>${esc(name)}</b>:命中 ${Object.keys(hits).length} 控件 / 存在 ${seen.length} 控件 / 零命中缺口 ${gaps.length}(@ ${esc(data.meta.generated_at)})</summary><div class='gaplist'>`;
for (const g of gaps) html += `${lamp("off", "存在但零真输入命中")} ${esc(g)}<br>`;
html += "<b>已命中:</b><br>";
for (const k of Object.keys(hits).sort()) html += `${lamp("ok", "真输入命中 " + hits[k] + " 次")} ${esc(k)}×${hits[k]}<br>`;
html += "</div></details>";
}
document.getElementById("controlDrill").innerHTML = html || "<p class='warn'>探针登记缺数据——先跑探针与 tools/coverage_receipts.py</p>";
})();
// ── 四、本体对账 ──
(function () {
const on = C.ontology;
if (!on || !on.categories) { document.getElementById("ontologyGaps").innerHTML = "<p class='warn'>本体数据缺——先跑 tools/coverage_enum.py</p>"; return; }
const ex = ((C.exemptions || {}).ontology || []).map(s => String(s));
const hitTexts = [];
const pri = (C.probe_real_input || {});
for (const k in pri) for (const hk of Object.keys(pri[k].hits || {})) hitTexts.push(hk);
let html = `<p class="note">实体 ${esc(on.meta.entities_total)} · 断言文料 ${esc(on.meta.corpus_rows)} 行 · 首见批次 ${esc(on.meta.generated_at)}</p>`;
for (const cat of on.categories) {
const shown = cat.gaps.filter(g => !ex.includes(g.id));
const probed = shown.filter(g => hitTexts.some(h => h.indexOf(g.id) >= 0));
const pure = shown.filter(g => !probed.includes(g));
html += `<details><summary><b>${esc(cat.category)}</b>:${cat.total} 实体 / 关联 ${cat.matched} / 缺口 ${shown.length}(其中探针承载 ${probed.length})</summary><div class='gaplist'>`;
for (const g of probed) html += `${lamp("ok", "探针真输入命中承载")} ${esc(g.id)} <span class="note">(断言文料零关联——交互覆盖由探针承载)</span><br>`;
for (const g of pure) html += `${lamp("off", "验证零关联缺口——人工复核后再立事项")} ${esc(g.id)}<br>`;
html += "</div></details>";
}
document.getElementById("ontologyGaps").innerHTML = html;
})();
</script>
</body>
</html>