#!/usr/bin/env python # -*- coding: utf-8 -*- """变异对照:证明自检的判据不是「恒绿」。 做法:把原型复制一份,往里注入一处**已知的假病**,再跑一次自检, 要求它必须报红,并且红在**预期的那一条**上。测完即删临时件。 为什么要这个:一个永远 PASS 的自检等于没有自检。只有「注病 → 报红 → 复原 → 回绿」 这条链走通了,前面那串 PASS 才算证据。 用法:python _tools/mutate_test.py """ import os, re, subprocess, sys, tempfile, html HERE = os.path.dirname(os.path.abspath(__file__)) UI = os.path.abspath(os.path.join(HERE, "..")) PROTO = os.path.join(UI, "mcn-workbench.html") PY = sys.executable # (名称, 说明, 查找串, 替换串, 期望出现的报红片段, 视口[可选,默认 1440x900]) MUTANTS = [ ("M1 死控件", "把 P2-01「低粉爆款」的实现掏空 —— 点它不再有任何反馈", "'P2-01':()=>{ S.benchFilter='低粉爆款'; paint(); },", "'P2-01':()=>{},", "P2-01"), ("M2 未登记的可点元素", "在页面上塞一个没写进交互清单的 data-act", "`;", " const close=``;", "中性关闭出口 = 2"), ] def read_out(dom_path): s = open(dom_path, encoding="utf-8", errors="replace").read() m = re.search(r'
(.*?)
', s, re.S) if not m: return None return html.unescape(re.sub(r"<[^>]+>", "", m.group(1))) def run(proto, vp="1440x900"): out = os.path.join(tempfile.gettempdir(), "mut-selftest.html") r = subprocess.run([PY, os.path.join(HERE, "cap.py"), "--mode", "dom", "--route", "selftest", "--viewport", vp, "--proto", proto, "--out", out], capture_output=True, text=True) if not os.path.exists(out): return None return read_out(out) def headline(text): m = re.search(r"自检结果:PASS (\d+) / FAIL (\d+)", text or "") return (int(m.group(1)), int(m.group(2))) if m else None def main(): src = open(PROTO, encoding="utf-8").read() base = run(PROTO) hb = headline(base) print("基线(未注入):PASS %s / FAIL %s" % hb if hb else "基线读不到自检输出") if not hb or hb[1] != 0: print("⛔ 基线就不是全绿 ⇒ 先修原型,别急着做变异对照。") return 2 bad = 0 for row in MUTANTS: name, why, find, repl, expect = row[:5] vp = row[5] if len(row) > 5 else "1440x900" if find not in src: print("⚠️ %s:找不到注入点,跳过(注入点失效本身也要修)" % name) bad += 1 continue tmp = os.path.join(tempfile.gettempdir(), "mut-proto.html") with open(tmp, "w", encoding="utf-8") as f: f.write(src.replace(find, repl, 1)) text = run(tmp, vp) h = headline(text) os.remove(tmp) if not h: print("❌ %s:注入后自检跑不出来(连输出都没有)" % name) bad += 1 continue caught = h[1] > 0 and expect in (text or "") print("%s %s:注入(%s) ⇒ PASS %d / FAIL %d | %s" % ("✅" if caught else "❌", name, why, h[0], h[1], "报红且红在预期处(%s,视口 %s)" % (expect, vp) if caught else "**没在预期处报红(期望含「%s」,视口 %s)**" % (expect, vp))) if not caught: bad += 1 print() print("变异对照:%d / %d 通过" % (len(MUTANTS) - bad, len(MUTANTS))) if bad == 0: print("⇒ %d 处假病全部被自检抓住,且基线回绿:这套判据不是恒绿的。" % len(MUTANTS)) return 0 if bad == 0 else 1 if __name__ == "__main__": sys.exit(main())