q63: q63_verify_scan.py -- classify wiring as invocation (python3/runpy/import/./), directory-scoped so a shared basename cannot cross-wire, and exclude the measuring instruments; 6 of 87 stripped-assert at 89cda97 -> 0
This commit is contained in:
1 parent
2a484e4873
commit
9cec6ca966
1 file changed
+85
-12
@@ -28,11 +28,20 @@ WHY THIS EXISTS
|
||||
itself names all eight basenames. Prose about a
|
||||
defect is not wiring for it.
|
||||
code-grep the same grep over code pathspecs only
|
||||
('*.py' '*.sh' 'CHECK' '*.yml' '*.yaml'):
|
||||
73 of 87 unwired at `c033059`. No gate, test or
|
||||
CHECK invokes them; `unittest discover` matches
|
||||
`test_*` and no CHECK globs `verify_*.py`.
|
||||
This scan grades the CODE definition, and holds the result in
|
||||
('*.py' '*.sh' CHECK '*.yml' '*.yaml'): 73 of 87
|
||||
unwired -- still too generous, because a basename
|
||||
spelled in a docstring, a report string or a
|
||||
git-show snapshot path is a mention, not a runner.
|
||||
invoke-grep the same grep with every reference classified:
|
||||
python3 X / runpy / import / ./X counts as
|
||||
wiring, anything else is a mention, and a bare
|
||||
basename only wires the file in its own directory
|
||||
(`verify_archive.py` exists in four sphinx
|
||||
archives). Result at `c033059`: 80 of 87 unwired,
|
||||
7 with an invocation-shaped line, and `--why`
|
||||
prints those seven lines so the figure is read
|
||||
rather than trusted.
|
||||
This scan grades the INVOKE definition, and holds the result in
|
||||
a tolerance ledger rather than a claim: `q63_unwired_baseline.tsv`
|
||||
names every unwired file measured today with its class
|
||||
(`declared-one-off` when the file says so in-file, `undeclared`
|
||||
@@ -73,6 +82,18 @@ EXIT_RE = re.compile(r"\b(?:sys\.)?exit\(\s*([^)]*?)\s*\)")
|
||||
CODE_PATHSPECS = ["*.py", "*.sh", "CHECK", "*.yml", "*.yaml"]
|
||||
DECLARED_RE = re.compile(r"Q63", re.I)
|
||||
DECLARED_PHRASE = "no committed gate invokes"
|
||||
# The measuring instruments name every file in the class; naming is not running.
|
||||
INSTRUMENT_DIR = "wizard-work/volt/"
|
||||
# INVOCATION, not mention. A code file that spells the basename in a git-show
|
||||
# string, a docstring or a report wires nothing -- the first version of this
|
||||
# scanner counted those as wired, and its own proof harness "wired" five files
|
||||
# it only read from a blob. A reference counts only in a shape that runs it.
|
||||
INVOKE_PATTERNS = [
|
||||
r"(?:python3?|python|sys\.executable|\$PY)[^\n]*{base}", # python3 verify_x.py
|
||||
r"(?:runpy|run_path|run_module|importlib)[^\n]*{base}", # runpy.run_path("verify_x.py")
|
||||
r"(?:import\s+{stem}\b)", # import verify_x
|
||||
r"(?:^|[;&|]\s*|\./|bash\s+|sh\s+){base}", # ./verify_x.py
|
||||
]
|
||||
|
||||
|
||||
def git(repo, *args):
|
||||
@@ -94,6 +115,49 @@ def exit_safe(src):
|
||||
return False
|
||||
|
||||
|
||||
def wiring_refs(repo, rev, path, base, stem):
|
||||
"""(invocations, mentions) for one basename at one revision.
|
||||
|
||||
git grep gives the lines; each line is classified. Two accuracy rules, both
|
||||
paid for by measurement rather than assumed:
|
||||
|
||||
* a reference counts only in an INVOCATION shape (python3 X, runpy/import,
|
||||
./X). A docstring, a report string or a git-show snapshot path is a
|
||||
MENTION: it wires nothing, and counting it would hide a dead verifier.
|
||||
* a bare basename is directory-local. `verify_archive.py` exists in four
|
||||
sphinx archives; a reference from another archive's directory is a
|
||||
reference to ITS sibling, not to this file. So a wiring reference must
|
||||
either name this file's parent directory, or sit in the same directory.
|
||||
"""
|
||||
args = ["grep", "-F", "-n", "--", base]
|
||||
if rev != "WORKTREE":
|
||||
args.append(rev)
|
||||
args += ["--", *CODE_PATHSPECS]
|
||||
try:
|
||||
lines = git(repo, *args).splitlines()
|
||||
except RuntimeError:
|
||||
return [], [] # git grep rc 1 == no match
|
||||
inv, men = [], []
|
||||
mydir = os.path.dirname(path)
|
||||
for ln in lines:
|
||||
# git grep output is REV:path:line:text at a rev, path:line:text on disk
|
||||
rest = ln.split(":", 1)[1] if rev != "WORKTREE" else ln
|
||||
where = rest.split(":", 2)[0]
|
||||
if where == path:
|
||||
continue # the file's own text is not a reference
|
||||
if where.startswith(INSTRUMENT_DIR):
|
||||
continue # the instruments that measure this class
|
||||
hit = any(re.search(p.format(base=re.escape(base), stem=re.escape(stem)), rest)
|
||||
for p in INVOKE_PATTERNS)
|
||||
same_dir = os.path.dirname(where) == mydir
|
||||
names_dir = mydir and (mydir + "/") in rest
|
||||
if hit and (same_dir or names_dir):
|
||||
inv.append(ln)
|
||||
else:
|
||||
men.append(ln)
|
||||
return inv, men
|
||||
|
||||
|
||||
def scan(repo, rev):
|
||||
if rev == "WORKTREE":
|
||||
names = [n for n in git(repo, "ls-files").splitlines() if VERIFY_RE.search(n)]
|
||||
@@ -109,19 +173,16 @@ def scan(repo, rev):
|
||||
for n in sorted(names):
|
||||
src = blobs[n]
|
||||
base = n.rsplit("/", 1)[-1]
|
||||
stem = base[:-3]
|
||||
bare = len(ASSERT_RE.findall(src))
|
||||
refs = []
|
||||
try:
|
||||
refs = [ln for ln in git(repo, "grep", "-F", "-l", "--", base, rev,
|
||||
"--", *CODE_PATHSPECS).splitlines() if ln != f"{rev}:{n}"]
|
||||
except RuntimeError:
|
||||
refs = [] # git grep rc 1 == no match: unwired
|
||||
refs, mentions = wiring_refs(repo, rev, n, base, stem)
|
||||
declared = bool(DECLARED_RE.search(src)) and DECLARED_PHRASE in src
|
||||
faults = []
|
||||
if bare and not exit_safe(src):
|
||||
faults.append("STRIPPED_ASSERT")
|
||||
rows.append({"path": n, "lane": n.split("/")[1], "asserts": bare,
|
||||
"exit_safe": exit_safe(src), "wired_refs": refs,
|
||||
"mention_refs": mentions,
|
||||
"declared_one_off": declared, "faults": faults})
|
||||
return rows
|
||||
|
||||
@@ -183,7 +244,7 @@ def emit_baseline(rows, path):
|
||||
out = ["# Q63 unwired-verifier baseline -- the unwired population this archive",
|
||||
"# already carried when the rule was written. One row per file: <class>\\t<path>.",
|
||||
"# class declared-one-off = the file states in-file that no gate invokes it;",
|
||||
"# class undeclared = measured unwired, no such statement (the Q64 residual).",
|
||||
"# class undeclared = measured unwired, no such statement (the Q65 residual).",
|
||||
"# A row is a stated tolerance, not an exemption: an unwired file that is NOT",
|
||||
"# listed makes q63_verify_scan.py rc 1, and a stripped-assert file always does.",
|
||||
"# Regenerate: q63_verify_scan.py --repo . --emit-baseline"]
|
||||
@@ -260,6 +321,8 @@ def main():
|
||||
ap.add_argument("--emit-baseline", action="store_true")
|
||||
ap.add_argument("--json")
|
||||
ap.add_argument("--verbose", action="store_true")
|
||||
ap.add_argument("--why", action="store_true",
|
||||
help="print the lines counted as wiring and as mention, per file")
|
||||
ap.add_argument("--selftest", action="store_true")
|
||||
a = ap.parse_args()
|
||||
if a.selftest:
|
||||
@@ -273,6 +336,16 @@ def main():
|
||||
emit_baseline(rows, a.baseline)
|
||||
return 0
|
||||
faults = report(rows, read_baseline(a.baseline), a.rev, a.verbose)
|
||||
if a.why:
|
||||
print("\nwiring detail (what the count is made of):")
|
||||
for r in rows:
|
||||
if not (r["wired_refs"] or r["mention_refs"]):
|
||||
continue
|
||||
print(f" {r['path']}")
|
||||
for ln in r["wired_refs"]:
|
||||
print(f" WIRED {ln[:150]}")
|
||||
for ln in r["mention_refs"][:3]:
|
||||
print(f" mention {ln[:150]}")
|
||||
if a.json:
|
||||
stripped, unwired, new_unwired, stale = grade(rows, read_baseline(a.baseline))
|
||||
with open(a.json, "w", encoding="utf-8") as f:
|
||||
|
||||
Reference in new issue
Block a user