From 266d6a32f85da303e96f65fce857525bf6013544 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 22 Sep 2026 20:41:30 -0400 Subject: [PATCH] [tools] (P7): the wire declines audit carries a self-test whose fixtures must red, and the CI step runs it before the tree audit Fable review of the B3 rework, should-fix: the audit had no negative control, so a green run could not say it would have been red. It now takes --self-test, which writes each inline fixture as a one-file renderer tree in a temp dir, runs the audit on it and checks both the rc and the line the audit must print (so a red is red for the fixture's reason): comment-only site (//) -> rc 1, "has NO SITE" comment-only site (/* */) -> rc 1, "has NO SITE" string-literal site -> rc 1, "has NO SITE" other-branch log -> rc 1, "with no MGLOG_W/E in its own block" nested-branch log -> rc 1, same log only in a /* */ comment above -> rc 1, same nearest log is MGLOG_D -> rc 1, same good sites (AT form, bare Count under its own MGLOG_E_ONCE, def row with a trailing comment) -> rc 0, "0 unlogged, 0 unknown" 8/8, rc 0. Mutants: with the strip disabled 5 of 8 fail (the four comment/string reds go green and the good fixture's commented row is lost); with the old 8-line window in place of the block walk 2 of 8 fail (other-branch, nested-branch). Against the audit as it stood at aab01275, six of the seven red fixtures passed and the good one failed. .github/workflows/test.yml: the "Wire declines audit (B3)" step runs --self-test and then the tree audit, the way the next step runs trace_cases.py --self-test-transport-matrices before it computes matrices. --- .github/workflows/test.yml | 8 ++- scripts/ci/wire_declines_audit.py | 99 +++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 475d0a31..0c93a2ca 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2393,9 +2393,13 @@ jobs: run: python3 scripts/ci/fatal_census.py # The decline half of rule I (P7 B3): every WireDeclines.def row has a site, every site a - # warning-or-error line. Same shape as the census - tree-only, no build. + # warning-or-error line. Same shape as the census - tree-only, no build. The self-test + # runs first: its fixtures (a site only in a comment or a string, a log in another + # branch, an MGLOG_D) must red, so a green audit is one that could have been red. - name: Wire declines audit (B3) - run: python3 scripts/ci/wire_declines_audit.py + run: | + python3 scripts/ci/wire_declines_audit.py --self-test + python3 scripts/ci/wire_declines_audit.py - name: Load trace cases id: trace-cases diff --git a/scripts/ci/wire_declines_audit.py b/scripts/ci/wire_declines_audit.py index 1fe1edf2..ed792e82 100644 --- a/scripts/ci/wire_declines_audit.py +++ b/scripts/ci/wire_declines_audit.py @@ -24,10 +24,14 @@ Everything is matched on the text with comments and string/character literals bl or only a string, is not one. usage: wire_declines_audit.py [repo-root] + wire_declines_audit.py --self-test (negative controls on inline fixtures) """ +import contextlib +import io import os import re import sys +import tempfile RENDERER = "MobileGL/MG_Backend/DirectVulkan/Renderer" DEF = os.path.join(RENDERER, "WireDeclines.def") @@ -184,7 +188,102 @@ def audit(root: str) -> int: return 1 if bad else 0 +# --self-test: each fixture is (name, def rows text, Fixture.cpp text, expected rc, text the +# audit must print - so a red is red for the reason the fixture is about). The red ones are +# the holes the B3 fix round closed; the green one proves the audit still accepts the two +# shapes the tree uses. +NO_SITE = "has NO SITE" +NO_LOG = "with no MGLOG_W/E in its own block" +_DEF_GHOST = "MGL_WIRE_DECLINE(GhostRow)\n" +SELF_TEST_FIXTURES = ( + ("comment-only site (//)", _DEF_GHOST, + "void F() {\n" + " // MGL_WIRE_DECLINE_AT(GhostRow, \"never counted\");\n" + "}\n", 1, NO_SITE), + ("comment-only site (/* */)", _DEF_GHOST, + "void F() {\n" + " /* retired:\n" + " MGL_WIRE_DECLINE_AT(GhostRow, \"never counted\"); */\n" + "}\n", 1, NO_SITE), + ("string-literal site", _DEF_GHOST, + "const char* kDoc = \"MGL_WIRE_DECLINE_AT(GhostRow, x);\";\n", 1, NO_SITE), + ("other-branch log", _DEF_GHOST, + "void F(int x) {\n" + " if (x == 1) {\n" + " MGLOG_W(\"x is one\");\n" + " }\n" + " if (x == 2) {\n" + " WireDeclineTally::Count(WireDeclineSite::GhostRow);\n" + " }\n" + "}\n", 1, NO_LOG), + ("nested-branch log", _DEF_GHOST, + "void F(int x) {\n" + " if (x) {\n" + " if (x == 1) {\n" + " MGLOG_W(\"x is one\");\n" + " }\n" + " WireDeclineTally::Count(WireDeclineSite::GhostRow);\n" + " }\n" + "}\n", 1, NO_LOG), + ("log only in a /* */ comment above", _DEF_GHOST, + "void F(int x) {\n" + " if (x) {\n" + " /* MGLOG_W(\"x\"); */\n" + " WireDeclineTally::Count(WireDeclineSite::GhostRow);\n" + " }\n" + "}\n", 1, NO_LOG), + ("nearest log is MGLOG_D", _DEF_GHOST, + "void F(int x) {\n" + " if (x) {\n" + " MGLOG_D(\"compiled away in Release\");\n" + " WireDeclineTally::Count(WireDeclineSite::GhostRow);\n" + " }\n" + "}\n", 1, NO_LOG), + ("good sites (AT form, bare Count under its own W/E, def row with trailing comment)", + "MGL_WIRE_DECLINE(GoodAt) // the macro form\nMGL_WIRE_DECLINE(GoodBare)\n", + "bool F(int x) {\n" + " if (x == 1) {\n" + " MGL_WIRE_DECLINE_AT(GoodAt, \"x=%d\", x);\n" + " return false;\n" + " }\n" + " if (x == 2) {\n" + " MGLOG_E_ONCE(\"x is two: %d, \"\n" + " \"declined\", x);\n" + " WireDeclineTally::Count(WireDeclineSite::GoodBare);\n" + " return false;\n" + " }\n" + " return true;\n" + "}\n", 0, "0 unlogged, 0 unknown"), +) + + +def self_test() -> int: + failures = 0 + for name, def_text, src_text, want, why in SELF_TEST_FIXTURES: + with tempfile.TemporaryDirectory(prefix="wire-declines-selftest-") as root: + src_dir = os.path.join(root, RENDERER) + os.makedirs(src_dir) + with open(os.path.join(src_dir, "WireDeclines.def"), "w", encoding="utf-8") as f: + f.write(def_text) + with open(os.path.join(src_dir, "Fixture.cpp"), "w", encoding="utf-8") as f: + f.write(src_text) + captured = io.StringIO() + with contextlib.redirect_stdout(captured), contextlib.redirect_stderr(captured): + got = audit(root) + ok = got == want and why in captured.getvalue() + failures += 0 if ok else 1 + print("wire-declines self-test: %-4s %s -> rc %d (want %d, printing \"%s\")" + % ("ok" if ok else "FAIL", name, got, want, why)) + if not ok: + sys.stdout.write("".join(" " + l + "\n" for l in captured.getvalue().splitlines())) + print("wire-declines self-test: %d/%d fixtures as expected" + % (len(SELF_TEST_FIXTURES) - failures, len(SELF_TEST_FIXTURES))) + return 1 if failures else 0 + + def main() -> int: + if len(sys.argv) > 1 and sys.argv[1] == "--self-test": + return self_test() return audit(os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "."))