ComfyUI-Manager/scripts/verify_audit_counts.py
Dr.Lt.Data 4410ebc6a6
Some checks are pending
Publish to PyPI / build-and-publish (push) Waiting to run
Python Linting / Run Ruff (push) Waiting to run
fix(security): harden CSRF with Content-Type gate and expand E2E coverage (#2818)
Defense-in-depth over GET→POST alone: reject the three CORS-safelisted
simple-form Content-Types (x-www-form-urlencoded, multipart/form-data,
text/plain) on 16 no-body POST handlers (glob + legacy) to block
<form method=POST> CSRF that bypasses method-only gating. Move
comfyui_switch_version to a JSON body so the preflight requirement applies.
Split db_mode/policy/update/channel_url_list into GET(read) + POST(write).
Tighten do_fix (high → high+) and gate three previously-ungated config
setters at middle. Resynchronize openapi.yaml (27 paths, 30 operations,
ComfyUISwitchVersionParams as a shared $ref component). Add E2E harness
variants, Playwright config, CSRF/secgate suites, 39-endpoint coverage,
and a CHANGELOG.

Breaking: legacy per-op POST routes (install/uninstall/fix/disable/update/
reinstall/abort_current) are removed; callers already use queue/batch.
Legacy /manager/notice (v1) is removed; /v2/manager/notice is retained.

Reported-by: XlabAI Team of Tencent Xuanwu Lab
CVSS: 8.1 (AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H)
2026-04-22 05:04:30 +09:00

186 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""
Self-verify reports/e2e_verification_audit.md tally consistency.
For each test-file section, parse the verdict column of every row in the
section's table and count PASS / WEAK / INADEQUATE / N/A symbols. Cross-check
against (a) the section's own "File verdict: ..." line and (b) the Summary
Matrix row for that filename.
Exit 0 on full consistency, 1 on any mismatch.
"""
from __future__ import annotations
import os
import re
import sys
from pathlib import Path
AUDIT = Path(__file__).resolve().parent.parent / "reports" / "e2e_verification_audit.md"
PASS_SYM = "\u2705"
WEAK_SYM = "\u26a0"
INADEQ_SYM = "\u274c"
SECTION_RE = re.compile(
r"^(?:# Section \d+|## \d+\.)\s+\u2014?\s*(?P<file>\S+\.(?:py|spec\.ts))"
)
VERDICT_LINE_RE = re.compile(r"^\*\*File verdict\*\*:\s*(?P<body>.+)$")
SUMMARY_ROW_RE = re.compile(
r"^\|\s*(?P<file>[^|]+?)\s*\|"
r"\s*(?P<p>\d+)\s*\|"
r"\s*(?P<w>\d+)\s*\|"
r"\s*(?P<i>\d+)\s*\|"
r"\s*(?P<n>\d+)\s*\|"
r"\s*(?P<t>\d+)\s*\|\s*$"
)
TOTAL_ROW_RE = re.compile(
r"^\|\s*\*\*TOTAL\*\*\s*\|"
r"\s*\*\*(?P<p>\d+)\*\*\s*\|"
r"\s*\*\*(?P<w>\d+)\*\*\s*\|"
r"\s*\*\*(?P<i>\d+)\*\*\s*\|"
r"\s*\*\*(?P<n>\d+)\*\*\s*\|"
r"\s*\*\*(?P<t>\d+)\*\*\s*\|\s*$"
)
ALL_N_TESTS_RE = re.compile(r"All\s+(\d+)\s+tests", re.IGNORECASE)
def count_symbols(line: str) -> tuple[int, int, int, int]:
p = line.count(PASS_SYM)
w = line.count(WEAK_SYM)
i = line.count(INADEQ_SYM)
n = 0
if re.search(r"\|\s*N/A\s*\|", line) or re.search(r"\|\s*N/A\s*\u2014", line):
bulk = ALL_N_TESTS_RE.search(line)
n = int(bulk.group(1)) if bulk else 1
return p, w, i, n
def parse_file_verdict(body: str) -> tuple[int, int, int, int]:
p = w = i = n = 0
for match in re.finditer(r"(\d+)/\d+\s*(\S)", body):
num, sym = int(match.group(1)), match.group(2)
if sym == PASS_SYM:
p = num
elif sym == WEAK_SYM:
w = num
elif sym == INADEQ_SYM:
i = num
n_match = re.search(r"(\d+)/\d+\s*N/A", body)
if n_match:
n = int(n_match.group(1))
return p, w, i, n
def main() -> int:
content = AUDIT.read_text(encoding="utf-8").splitlines()
sections: dict[str, dict[str, tuple[int, int, int, int]]] = {}
current_file: str | None = None
row_tally = (0, 0, 0, 0)
in_table = False
for line in content:
m = SECTION_RE.match(line)
if m:
if current_file is not None:
sections.setdefault(current_file, {})["rows"] = row_tally
current_file = os.path.basename(m.group("file").strip())
row_tally = (0, 0, 0, 0)
in_table = False
continue
if current_file is None:
continue
if line.startswith("| Test ") or re.match(r"^\|\s*-+", line) or line.startswith("|---"):
in_table = True
continue
if in_table and line.startswith("|"):
p, w, i, n = count_symbols(line)
row_tally = (row_tally[0] + p, row_tally[1] + w, row_tally[2] + i, row_tally[3] + n)
continue
if in_table and not line.startswith("|"):
in_table = False
fv = VERDICT_LINE_RE.match(line)
if fv and current_file:
sections.setdefault(current_file, {})["file_verdict"] = parse_file_verdict(fv.group("body"))
if current_file is not None:
sections.setdefault(current_file, {})["rows"] = row_tally
summary_rows: dict[str, tuple[int, int, int, int, int]] = {}
total_row: tuple[int, int, int, int, int] | None = None
for line in content:
tm = TOTAL_ROW_RE.match(line)
if tm:
total_row = (
int(tm.group("p")), int(tm.group("w")), int(tm.group("i")),
int(tm.group("n")), int(tm.group("t")),
)
continue
sm = SUMMARY_ROW_RE.match(line)
if sm:
name = sm.group("file").strip()
if name.lower() == "file" or name.startswith("**") or name.startswith("---"):
continue
summary_rows[name] = (
int(sm.group("p")), int(sm.group("w")), int(sm.group("i")),
int(sm.group("n")), int(sm.group("t")),
)
ok = True
print(f"Audit: {AUDIT}")
print(f"Sections parsed: {len(sections)} Summary rows parsed: {len(summary_rows)}")
print("-" * 90)
hdr = f"{'File':44} {'Rows(P W I N)':16} {'FileVerdict':16} {'Summary(P W I N T)':20} OK"
print(hdr)
print("-" * 90)
section_totals = [0, 0, 0, 0, 0]
for name, data in sections.items():
rows = data.get("rows", (0, 0, 0, 0))
fv = data.get("file_verdict", (0, 0, 0, 0))
sm_row = summary_rows.get(name)
row_str = "{} {} {} {}".format(*rows)
fv_str = "{} {} {} {}".format(*fv) if fv != (0, 0, 0, 0) else "(none)"
sm_str = "{} {} {} {} {}".format(*sm_row) if sm_row else "(missing)"
row_matches_sm = sm_row is not None and rows == sm_row[:4]
fv_matches_rows = fv == rows
this_ok = row_matches_sm and fv_matches_rows
mark = "\u2713" if this_ok else "\u2717"
if not this_ok:
ok = False
print(f"{name:44} {row_str:16} {fv_str:16} {sm_str:20} {mark}")
if sm_row:
for idx in range(5):
section_totals[idx] += sm_row[idx]
unseen_summary = set(summary_rows.keys()) - set(sections.keys())
for name in unseen_summary:
sm_row = summary_rows[name]
for idx in range(5):
section_totals[idx] += sm_row[idx]
print(f"{name:44} {'(no section)':16} {'(n/a)':16} {'{} {} {} {} {}'.format(*sm_row):20} -")
print("-" * 90)
if total_row:
reported_total = total_row
computed = tuple(section_totals)
total_ok = reported_total == computed
mark = "\u2713" if total_ok else "\u2717"
print(f"{'TOTAL (from Summary)':44} reported={reported_total} computed={computed} {mark}")
if not total_ok:
ok = False
else:
print("TOTAL row NOT FOUND")
ok = False
print()
print("PASS" if ok else "FAIL")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())