volt: Q168 land -- the append audit grades the pin axis beside the index axis
queue_append_audit.py now runs two graders out of one `git archive`: grade_index
(unchanged vocabulary) and grade_pin, which runs the rev's own open_set.py
--check beside the rev's own queue. PASS / DRIFT / UNGRADABLE / ERROR; rc 1 when
either axis is fatal, so --guard means "index current and pin moved".
--axis {both,index,pin} narrows the run; the report prints index:/pin:/drift:.
Checks landed with it: CHECK-append-pin (5 legs, rc 0 here).
Measured at 12fb1b0's parent line: --selftest 43/43; --guard rc 0 pin:PASS;
1c3c855..56d3853 rc 1 pin:DRIFT "count headings moved 116 -> 117" while
--axis index over the same range reads rc 0, and 12fb1b0's pre-axis blob reads
rc 0 too, so the red is the axis and not the range.
PIN_COUNTS moved to the post-append counts (119 headings/103 ids, 131
records/97 ids) and docs/queue-index.md re-rendered; docs/TOOLS.md row follows
the tool's flags.
This commit is contained in:
1 parent
16332d6709
commit
1ec068cae0
7 files changed
+487
-95
No files matched your search
+276
-78
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""queue_append_audit.py -- was the queue's index current IN THE TREE of every
|
||||
commit that touched the queue?
|
||||
"""queue_append_audit.py -- did every queue-touching commit leave its own tree
|
||||
consistent: its index current, and its pin moved to where the queue's counts
|
||||
went?
|
||||
|
||||
WHY THIS EXISTS
|
||||
`docs/queue-index.md` is rendered from `WORK-QUEUE.md` and pins it by sha256,
|
||||
@@ -22,7 +23,9 @@ WHY THIS EXISTS
|
||||
the gate as it existed there.
|
||||
|
||||
WHAT IT MEASURES -- one row per commit that touches the queue in the range
|
||||
verdict PASS the commit's own tree passes its own --check (rc 0)
|
||||
verdict the INDEX axis: the commit's own tree passes its own
|
||||
`tools/queue_index.py --check` (rc 0)
|
||||
PASS rc 0
|
||||
STALE --check graded a stale rendered document (rc 1)
|
||||
UNGRADABLE the gate did not exist in that tree (no
|
||||
tools/queue_index.py, or no index document), or the
|
||||
@@ -32,6 +35,19 @@ WHAT IT MEASURES -- one row per commit that touches the queue in the range
|
||||
bucket is the exact defect this tool was built after.
|
||||
ERROR any other exit, a crash, or a timeout. rc 1 without
|
||||
the tool's `STALE:` line is a crash, not a fault.
|
||||
pin the PIN axis: the same tree's own
|
||||
`wizard-work/volt/q137-open-set/open_set.py --check`, which
|
||||
compares WORK-QUEUE.md's counts with the PIN_COUNTS that file
|
||||
carries.
|
||||
PASS rc 0, the pin moved with the append
|
||||
DRIFT rc 1, the queue moved and the pin did not
|
||||
UNGRADABLE no open_set.py at that rev (the pin is younger than
|
||||
the queue), or the tool refused with rc 2
|
||||
ERROR any other exit, a crash, or a timeout
|
||||
The two axes are graded in ONE materialised tree and reported
|
||||
side by side, because neither stands for the other: `56d3853`
|
||||
re-rendered its index and left the pin two headings behind, so
|
||||
its row reads `PASS` on the index and `DRIFT` on the pin.
|
||||
queue_add/del numstat for the queue in that commit.
|
||||
index_changed whether the same commit also changed the index document -- the
|
||||
mechanical shape of the rule, reported BESIDE the verdict and
|
||||
@@ -41,7 +57,8 @@ WHAT IT MEASURES -- one row per commit that touches the queue in the range
|
||||
would be such a case, and the verdict would say so).
|
||||
|
||||
Precedence is deliberate: the verdict comes from a checkout of the commit,
|
||||
never from its message, and never from a run at HEAD.
|
||||
never from its message, and never from a run at HEAD. `--axis` narrows the run
|
||||
to one of the two axes; the default grades both.
|
||||
|
||||
WHAT IT DOES NOT DO
|
||||
It repairs nothing, decides nothing about whether an UNGRADABLE commit should
|
||||
@@ -49,7 +66,7 @@ WHAT IT DOES NOT DO
|
||||
`tools/queue_claim_check.py`'s verdict. `--guard` is the same measurement
|
||||
restricted to the NEWEST queue-touching commit, which is the shape a tick's
|
||||
`check.sh` can call: rc 0 means the commit about to be pushed left the index
|
||||
current in its own tree.
|
||||
current AND the pin moved in its own tree.
|
||||
|
||||
EXIT CODES (house contract, decided 2026-10-17 at 6ee8fce)
|
||||
0 ran; nothing graded faulty (UNGRADABLE rows are reported, not faults)
|
||||
@@ -78,8 +95,15 @@ import tempfile
|
||||
QUEUE_DEFAULT = "WORK-QUEUE.md"
|
||||
INDEX_DEFAULT = "docs/queue-index.md"
|
||||
GATE_TOOL = "tools/queue_index.py"
|
||||
# The queue's own pin, graded in the SAME materialised tree: `open_set.py
|
||||
# --check` compares WORK-QUEUE.md's counts against the PIN_COUNTS the file
|
||||
# carries. The index axis cannot see it -- `56d3853` rendered its index and left
|
||||
# the pin two headings behind -- so the audit grades both axes, each reported
|
||||
# beside the other and neither standing for the other.
|
||||
PIN_TOOL = "wizard-work/volt/q137-open-set/open_set.py"
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
FATAL = ("STALE", "ERROR")
|
||||
PIN_FATAL = ("DRIFT", "ERROR")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- git plumbing
|
||||
@@ -166,61 +190,126 @@ def materialise(repo, rev, paths, dest):
|
||||
|
||||
# ------------------------------------------------------------------- one commit
|
||||
|
||||
def audit_commit(repo, rev, queue, index, timeout):
|
||||
"""Grade one commit IN ITS OWN TREE. Never raises."""
|
||||
def run_check(cmd, cwd, timeout):
|
||||
"""(rc, stdout, stderr, why) -- a timeout is a verdict, never a traceback."""
|
||||
try:
|
||||
p = subprocess.run(cmd, cwd=cwd, capture_output=True, timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
return None, "", "", "--check timed out after %ss" % timeout
|
||||
return (p.returncode, p.stdout.decode("utf-8", "replace"),
|
||||
p.stderr.decode("utf-8", "replace"), "")
|
||||
|
||||
|
||||
def grade_index(td, queue, index, timeout):
|
||||
"""The index axis, measured in `td`: {verdict, rc, detail}."""
|
||||
tool = os.path.join(td, GATE_TOOL)
|
||||
cmd = [sys.executable, tool,
|
||||
"--repo", td,
|
||||
"--queue", os.path.join(td, queue),
|
||||
"--check", os.path.join(td, index)]
|
||||
rc, s, e, why = run_check(cmd, td, timeout)
|
||||
if why:
|
||||
return {"verdict": "ERROR", "rc": None, "detail": why}
|
||||
first = (s.strip() or e.strip() or "").splitlines()
|
||||
out = {"rc": rc, "detail": first[0][:180] if first else ""}
|
||||
if rc == 0:
|
||||
out["verdict"] = "PASS"
|
||||
elif rc == 1 and "STALE:" in s:
|
||||
out["verdict"] = "STALE"
|
||||
elif rc == 2:
|
||||
out["verdict"] = "UNGRADABLE"
|
||||
else:
|
||||
# rc 1 with no STALE line is a crash wearing the verdict code; rc 3
|
||||
# is retired. Neither is a graded fault, and neither is a pass.
|
||||
out["verdict"] = "ERROR"
|
||||
return out
|
||||
|
||||
|
||||
def grade_pin(td, timeout):
|
||||
"""The pin axis, measured in `td`: {pin, pin_rc, pin_detail}.
|
||||
|
||||
`open_set.py --check` compares the queue's own counts against the PIN_COUNTS
|
||||
the file carries, so a commit that appends a heading or a record and leaves
|
||||
the pin where it was reads DRIFT here even when its index is current -- the
|
||||
exact shape `56d3853` landed in, which the index axis read PASS.
|
||||
"""
|
||||
tool = os.path.join(td, PIN_TOOL)
|
||||
rc, s, e, why = run_check([sys.executable, tool, "--check"], td, timeout)
|
||||
if why:
|
||||
return {"pin": "ERROR", "pin_rc": None, "pin_detail": why}
|
||||
lines = (s.strip() or e.strip() or "").splitlines()
|
||||
fault = [ln for ln in lines if ln.startswith("FAIL")]
|
||||
out = {"pin_rc": rc, "pin_detail": (fault[0] if fault else (lines[-1] if lines else ""))[:180]}
|
||||
if rc == 0:
|
||||
out["pin"] = "PASS"
|
||||
elif rc == 1:
|
||||
out["pin"] = "DRIFT"
|
||||
elif rc == 2:
|
||||
# the pin tool itself refused: a tree it cannot read is not a drift
|
||||
out["pin"] = "UNGRADABLE"
|
||||
else:
|
||||
out["pin"] = "ERROR"
|
||||
return out
|
||||
|
||||
|
||||
def audit_commit(repo, rev, queue, index, timeout, axis="both"):
|
||||
"""Grade one commit IN ITS OWN TREE. Never raises.
|
||||
|
||||
Two axes, graded independently in one materialised tree, because neither
|
||||
stands for the other: a commit can leave the rendered index current and the
|
||||
queue's own pin two headings behind (Q167's landing), and a commit can move
|
||||
the pin while shipping no index at all.
|
||||
"""
|
||||
row = {"rev": rev}
|
||||
row.update(commit_meta(repo, rev))
|
||||
add, delete, touched_index = numstat(repo, rev, queue, index)
|
||||
row.update({"queue_add": add, "queue_del": delete,
|
||||
"index_changed": touched_index, "rc": None, "detail": ""})
|
||||
"index_changed": touched_index, "rc": None, "detail": "",
|
||||
"pin_rc": None, "pin_detail": ""})
|
||||
|
||||
want_index = axis in ("both", "index")
|
||||
want_pin = axis in ("both", "pin")
|
||||
|
||||
if not exists(repo, rev, queue):
|
||||
row.update(verdict="UNGRADABLE",
|
||||
detail="%s does not exist at this rev" % queue)
|
||||
missing = "%s does not exist at this rev" % queue
|
||||
row.update(verdict="UNGRADABLE", detail=missing,
|
||||
pin="UNGRADABLE", pin_detail=missing)
|
||||
return row
|
||||
# The gate as it existed AT THAT REV, not as it exists today: a revision
|
||||
# where queue_index.py had not been written yet has no gate, and no claim
|
||||
# about its index can be true or false.
|
||||
if not exists(repo, rev, GATE_TOOL):
|
||||
row.update(verdict="UNGRADABLE",
|
||||
detail="no %s at this rev (the gate did not exist yet)" % GATE_TOOL)
|
||||
return row
|
||||
if not exists(repo, rev, index):
|
||||
row.update(verdict="UNGRADABLE",
|
||||
detail="no %s at this rev (nothing to compare against)" % index)
|
||||
if want_index:
|
||||
# The gate as it existed AT THAT REV, not as it exists today: a revision
|
||||
# where queue_index.py had not been written yet has no gate, and no
|
||||
# claim about its index can be true or false.
|
||||
if not exists(repo, rev, GATE_TOOL):
|
||||
row.update(verdict="UNGRADABLE",
|
||||
detail="no %s at this rev (the gate did not exist yet)" % GATE_TOOL)
|
||||
want_index = False
|
||||
elif not exists(repo, rev, index):
|
||||
row.update(verdict="UNGRADABLE",
|
||||
detail="no %s at this rev (nothing to compare against)" % index)
|
||||
want_index = False
|
||||
if want_pin and not exists(repo, rev, PIN_TOOL):
|
||||
# the pin is younger than the queue: absent is UNGRADABLE, never DRIFT
|
||||
row.update(pin="UNGRADABLE",
|
||||
pin_detail="no %s at this rev" % PIN_TOOL)
|
||||
want_pin = False
|
||||
if not want_index and not want_pin:
|
||||
return row
|
||||
|
||||
wanted = [p for p in (queue, index, GATE_TOOL, PIN_TOOL) if exists(repo, rev, p)]
|
||||
td = tempfile.mkdtemp(prefix="qaa-")
|
||||
try:
|
||||
ok, why = materialise(repo, rev, [queue, index, GATE_TOOL], td)
|
||||
ok, why = materialise(repo, rev, wanted, td)
|
||||
if not ok:
|
||||
row.update(verdict="ERROR", detail="could not materialise: %s" % why)
|
||||
why = "could not materialise: %s" % why
|
||||
if want_index:
|
||||
row.update(verdict="ERROR", detail=why)
|
||||
if want_pin:
|
||||
row.update(pin="ERROR", pin_detail=why)
|
||||
return row
|
||||
tool = os.path.join(td, GATE_TOOL)
|
||||
cmd = [sys.executable, tool,
|
||||
"--repo", td,
|
||||
"--queue", os.path.join(td, queue),
|
||||
"--check", os.path.join(td, index)]
|
||||
try:
|
||||
p = subprocess.run(cmd, cwd=td, capture_output=True, timeout=timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
row.update(verdict="ERROR", detail="--check timed out after %ss" % timeout)
|
||||
return row
|
||||
s = p.stdout.decode("utf-8", "replace")
|
||||
e = p.stderr.decode("utf-8", "replace")
|
||||
first = (s.strip() or e.strip() or "").splitlines()
|
||||
row["detail"] = first[0][:180] if first else ""
|
||||
row["rc"] = p.returncode
|
||||
if p.returncode == 0:
|
||||
row["verdict"] = "PASS"
|
||||
elif p.returncode == 1 and "STALE:" in s:
|
||||
row["verdict"] = "STALE"
|
||||
elif p.returncode == 2:
|
||||
row["verdict"] = "UNGRADABLE"
|
||||
else:
|
||||
# rc 1 with no STALE line is a crash wearing the verdict code; rc 3
|
||||
# is retired. Neither is a graded fault, and neither is a pass.
|
||||
row["verdict"] = "ERROR"
|
||||
if want_index:
|
||||
row.update(grade_index(td, queue, index, timeout))
|
||||
if want_pin:
|
||||
row.update(grade_pin(td, timeout))
|
||||
return row
|
||||
finally:
|
||||
shutil.rmtree(td, ignore_errors=True)
|
||||
@@ -238,7 +327,7 @@ def queue_revs(repo, queue, rev_from, rev_to):
|
||||
|
||||
|
||||
def audit(repo, queue, index, rev_from, rev_to, guard=False, timeout=30,
|
||||
all_history=False):
|
||||
all_history=False, axis="both"):
|
||||
"""(rc, payload). The only place exit codes are decided.
|
||||
|
||||
Range rules, in order:
|
||||
@@ -253,8 +342,9 @@ def audit(repo, queue, index, rev_from, rev_to, guard=False, timeout=30,
|
||||
"""
|
||||
payload = {"repo": repo, "queue": queue, "index": index,
|
||||
"from": None, "to": None, "guard": bool(guard),
|
||||
"auto_from": False, "pre_gate": 0,
|
||||
"rows": [], "counts": {}, "graded": 0, "rc": None}
|
||||
"auto_from": False, "pre_gate": 0, "axis": axis,
|
||||
"rows": [], "counts": {}, "pin_counts": {},
|
||||
"graded": 0, "pin_graded": 0, "rc": None}
|
||||
|
||||
def _grade_error(msg):
|
||||
"""Attach a grade error to the payload and return the (2, payload) verdict.
|
||||
@@ -310,14 +400,21 @@ def audit(repo, queue, index, rev_from, rev_to, guard=False, timeout=30,
|
||||
except (ValueError, AttributeError):
|
||||
payload["pre_gate"] = 0
|
||||
|
||||
rows = [audit_commit(repo, r, queue, index, timeout) for r in revs]
|
||||
counts = {}
|
||||
rows = [audit_commit(repo, r, queue, index, timeout, axis) for r in revs]
|
||||
counts, pin_counts = {}, {}
|
||||
for r in rows:
|
||||
counts[r["verdict"]] = counts.get(r["verdict"], 0) + 1
|
||||
if "verdict" in r:
|
||||
counts[r["verdict"]] = counts.get(r["verdict"], 0) + 1
|
||||
if "pin" in r:
|
||||
pin_counts[r["pin"]] = pin_counts.get(r["pin"], 0) + 1
|
||||
payload["rows"] = rows
|
||||
payload["counts"] = counts
|
||||
payload["graded"] = counts.get("PASS", 0) + counts.get("STALE", 0) + counts.get("ERROR", 0)
|
||||
payload["rc"] = 1 if any(r["verdict"] in FATAL for r in rows) else 0
|
||||
payload["pin_counts"] = pin_counts
|
||||
payload["graded"] = sum(counts.get(k, 0) for k in ("PASS", "STALE", "ERROR"))
|
||||
payload["pin_graded"] = sum(pin_counts.get(k, 0) for k in ("PASS", "DRIFT", "ERROR"))
|
||||
index_fault = any(r.get("verdict") in FATAL for r in rows)
|
||||
pin_fault = any(r.get("pin") in PIN_FATAL for r in rows)
|
||||
payload["rc"] = 1 if (index_fault or pin_fault) else 0
|
||||
return payload["rc"], payload
|
||||
|
||||
|
||||
@@ -349,30 +446,47 @@ def report(payload, stream=sys.stdout):
|
||||
for r in payload["rows"]:
|
||||
add, dele = r["queue_add"], r["queue_del"]
|
||||
q = "q %s/%s" % (add if add is not None else "-", dele if dele is not None else "-")
|
||||
w(" %-10s %-8s %-25s %-9s index:%-3s %s\n"
|
||||
% (r["verdict"], r["short"], r["date"][:25], q,
|
||||
"yes" if r["index_changed"] else "no", r["detail"]))
|
||||
note = r.get("detail") or ""
|
||||
if r.get("pin") and r["pin"] != "PASS":
|
||||
note = "%s | pin: %s" % (note, r.get("pin_detail") or r["pin"])
|
||||
w(" %-10s %-8s %-25s %-9s index:%-3s pin:%-10s %s\n"
|
||||
% (r.get("verdict") or "-", r["short"], r["date"][:25], q,
|
||||
"yes" if r["index_changed"] else "no", r.get("pin") or "-", note))
|
||||
c = payload["counts"]
|
||||
pc = payload["pin_counts"]
|
||||
w("\nsummary: %d commit(s) -- %s\n"
|
||||
% (len(payload["rows"]),
|
||||
", ".join("%d %s" % (c[k], k) for k in ("PASS", "STALE", "UNGRADABLE", "ERROR") if k in c)
|
||||
or "nothing graded"))
|
||||
w("pin: %s\n"
|
||||
% (", ".join("%d %s" % (pc[k], k) for k in ("PASS", "DRIFT", "UNGRADABLE", "ERROR") if k in pc)
|
||||
or "not graded"))
|
||||
ungradable = c.get("UNGRADABLE", 0)
|
||||
w("graded: %d of %d%s\n"
|
||||
w("graded: %d of %d index, %d of %d pin%s\n"
|
||||
% (payload["graded"], len(payload["rows"]),
|
||||
(" (%d could not be graded -- reported, NOT counted as stale)" % ungradable)
|
||||
payload["pin_graded"], len(payload["rows"]),
|
||||
(" (%d index UNGRADABLE -- reported, NOT counted as stale)" % ungradable)
|
||||
if ungradable else ""))
|
||||
stale = [r["short"] for r in payload["rows"] if r["verdict"] == "STALE"]
|
||||
stale = [r["short"] for r in payload["rows"] if r.get("verdict") == "STALE"]
|
||||
if stale:
|
||||
w("stale: %s\n" % " ".join(stale))
|
||||
bad = [r["short"] for r in payload["rows"] if r["verdict"] == "ERROR"]
|
||||
drift = [r["short"] for r in payload["rows"] if r.get("pin") == "DRIFT"]
|
||||
if drift:
|
||||
w("drift: %s\n" % " ".join(drift))
|
||||
bad = [r["short"] for r in payload["rows"] if r.get("verdict") == "ERROR"]
|
||||
pbad = [r["short"] for r in payload["rows"] if r.get("pin") == "ERROR"]
|
||||
if bad:
|
||||
w("error: %s\n" % " ".join(bad))
|
||||
if pbad:
|
||||
w("pin err: %s\n" % " ".join(pbad))
|
||||
if payload["rc"] == 0:
|
||||
w("verdict: every graded commit left the index current in its own tree\n")
|
||||
w("verdict: every graded commit left its own tree consistent -- "
|
||||
"the index current and the pin where the queue put it\n")
|
||||
else:
|
||||
w("verdict: %d commit(s) did NOT leave the index current in their own tree -- "
|
||||
"rc 1\n" % (len(stale) + len(bad)))
|
||||
w("verdict: %d commit(s) left their own tree inconsistent -- "
|
||||
"%d index, %d pin -- rc 1\n"
|
||||
% (len(stale) + len(bad) + len(drift) + len(pbad), len(stale) + len(bad),
|
||||
len(drift) + len(pbad)))
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- selftest
|
||||
@@ -381,13 +495,40 @@ def _sh(cwd, *args):
|
||||
return subprocess.run(args, cwd=cwd, capture_output=True, text=True)
|
||||
|
||||
|
||||
def _fixture(base):
|
||||
"""A five-commit history with one commit of each kind. Returns (repo, revs).
|
||||
PIN_STUB = '''#!/usr/bin/env python3
|
||||
"""A stand-in for wizard-work/volt/q137-open-set/open_set.py's --check: count
|
||||
the queue's headings and compare them with the PIN this commit carries."""
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
A: queue only, no gate tool -> UNGRADABLE
|
||||
B: appends, and adds tools/queue_index.py + its render -> PASS
|
||||
C: appends to the queue, no re-render -> STALE
|
||||
D: appends AND re-renders -> PASS
|
||||
QUEUE = "WORK-QUEUE.md"
|
||||
PIN = @PIN@
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--check", action="store_true")
|
||||
ap.parse_args()
|
||||
|
||||
n = len([ln for ln in open(QUEUE, encoding="utf-8") if ln.startswith("## ")])
|
||||
print("queue: %d heading(s)" % n)
|
||||
if n != PIN:
|
||||
print("FAIL count headings moved %d -> %d" % (PIN, n))
|
||||
print("CHECK 1 fault(s)")
|
||||
sys.exit(1)
|
||||
print("CHECK OK: counts match the pin")
|
||||
sys.exit(0)
|
||||
'''
|
||||
|
||||
|
||||
def _fixture(base):
|
||||
"""A seven-commit history with one commit of each kind. Returns (repo, revs).
|
||||
|
||||
A: queue only, no gate tool -> index UNGRADABLE, pin UNGRADABLE
|
||||
B: appends, adds tools/queue_index.py + its render + the pin tool (PIN=1)
|
||||
-> both PASS
|
||||
C: appends to the queue, no re-render -> index STALE, pin PASS
|
||||
D: appends AND re-renders -> both PASS
|
||||
F: appends a HEADING and re-renders, pin left -> index PASS, pin DRIFT
|
||||
G: appends a record and moves the pin to 2 -> both PASS
|
||||
E: touches an unrelated file -> not selected at all
|
||||
"""
|
||||
repo = os.path.join(base, "repo")
|
||||
@@ -402,15 +543,19 @@ def _fixture(base):
|
||||
_sh(repo, "git", "add", "-A")
|
||||
_sh(repo, "git", "commit", "-q", "-m", "A: queue only")
|
||||
|
||||
pin_dir = os.path.join(repo, os.path.dirname(PIN_TOOL))
|
||||
os.makedirs(pin_dir)
|
||||
os.makedirs(os.path.join(repo, "tools"))
|
||||
os.makedirs(os.path.join(repo, "docs"))
|
||||
with open(os.path.join(repo, PIN_TOOL), "w", encoding="utf-8") as fh:
|
||||
fh.write(PIN_STUB.replace("@PIN@", "1"))
|
||||
with open(os.path.join(repo, GATE_TOOL), "w", encoding="utf-8") as fh:
|
||||
fh.write(open(os.path.join(HERE, "queue_index.py"), encoding="utf-8").read())
|
||||
with open(queue, "a", encoding="utf-8") as fh:
|
||||
fh.write("- beta appended\n")
|
||||
_sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT)
|
||||
_sh(repo, "git", "add", "-A")
|
||||
_sh(repo, "git", "commit", "-q", "-m", "B: append + gate + index")
|
||||
_sh(repo, "git", "commit", "-q", "-m", "B: append + gate + pin + index")
|
||||
|
||||
with open(queue, "a", encoding="utf-8") as fh:
|
||||
fh.write("- gamma appended\n")
|
||||
@@ -423,6 +568,23 @@ def _fixture(base):
|
||||
_sh(repo, "git", "add", "-A")
|
||||
_sh(repo, "git", "commit", "-q", "-m", "D: append + re-render")
|
||||
|
||||
# F: the shape Q167 landed in -- the index is re-rendered and current, the
|
||||
# pin is not moved, so the two axes disagree in ONE row.
|
||||
with open(queue, "a", encoding="utf-8") as fh:
|
||||
fh.write("\n## Q2 -- beta\n\nStatus: open\n")
|
||||
_sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT)
|
||||
_sh(repo, "git", "add", "-A")
|
||||
_sh(repo, "git", "commit", "-q", "-m", "F: new heading + re-render, pin left")
|
||||
|
||||
# G: the repair commit -- touches the queue and moves the pin with it.
|
||||
with open(queue, "a", encoding="utf-8") as fh:
|
||||
fh.write("- epsilon appended\n")
|
||||
with open(os.path.join(repo, PIN_TOOL), "w", encoding="utf-8") as fh:
|
||||
fh.write(PIN_STUB.replace("@PIN@", "2"))
|
||||
_sh(repo, sys.executable, GATE_TOOL, "--repo", ".", "--write", INDEX_DEFAULT)
|
||||
_sh(repo, "git", "add", "-A")
|
||||
_sh(repo, "git", "commit", "-q", "-m", "G: append + move the pin")
|
||||
|
||||
with open(os.path.join(repo, "notes.txt"), "w", encoding="utf-8") as fh:
|
||||
fh.write("unrelated\n")
|
||||
_sh(repo, "git", "add", "-A")
|
||||
@@ -440,12 +602,12 @@ def selftest():
|
||||
base = tempfile.mkdtemp(prefix="qaa-selftest-")
|
||||
try:
|
||||
repo, revs = _fixture(base)
|
||||
a, b, c, d, e = revs
|
||||
a, b, c, d, f, g, e = revs
|
||||
rc, p = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD",
|
||||
all_history=True)
|
||||
rows = {r["rev"]: r for r in p["rows"]}
|
||||
|
||||
check("the audit selects the 4 queue-touching commits, not the 5th", len(p["rows"]), 4)
|
||||
check("the audit selects the 6 queue-touching commits, not the 7th", len(p["rows"]), 6)
|
||||
check("a commit with no gate tool is UNGRADABLE, not STALE", rows[a]["verdict"], "UNGRADABLE")
|
||||
check("the commit that added the gate and its index is PASS", rows[b]["verdict"], "PASS")
|
||||
check("an append with no re-render is STALE", rows[c]["verdict"], "STALE")
|
||||
@@ -455,20 +617,52 @@ def selftest():
|
||||
check("the stale row's numstat is the append", (rows[c]["queue_add"], rows[c]["queue_del"]), (1, 0))
|
||||
check("a range with a STALE row exits 1", rc, 1)
|
||||
check("counts keep UNGRADABLE out of the STALE bucket", p["counts"].get("STALE"), 1)
|
||||
check("graded counts PASS+STALE+ERROR only", p["graded"], 3)
|
||||
check("graded counts PASS+STALE+ERROR only", p["graded"], 5)
|
||||
check("the STALE detail carries the tool's own word", "STALE:" in rows[c]["detail"], True)
|
||||
|
||||
# -- the pin axis, measured in the same trees -----------------------
|
||||
check("a commit that predates the pin tool is pin UNGRADABLE", rows[a]["pin"], "UNGRADABLE")
|
||||
check("the commit that shipped the pin reads pin PASS", rows[b]["pin"], "PASS")
|
||||
check("an index-stale commit whose pin matches reads pin PASS", rows[c]["pin"], "PASS")
|
||||
check("a new heading with the pin left behind reads pin DRIFT", rows[f]["pin"], "DRIFT")
|
||||
check("the DRIFT row's INDEX is still PASS -- the axes are independent",
|
||||
rows[f]["verdict"], "PASS")
|
||||
check("the DRIFT row names the count that moved",
|
||||
"count headings moved 1 -> 2" in rows[f]["pin_detail"], True)
|
||||
check("a queue-touching commit that moves the pin reads pin PASS", rows[g]["pin"], "PASS")
|
||||
check("DRIFT is kept out of the pin PASS bucket", p["pin_counts"].get("DRIFT"), 1)
|
||||
check("pin UNGRADABLE is counted, not folded into DRIFT", p["pin_counts"].get("UNGRADABLE"), 1)
|
||||
check("pin_graded counts PASS+DRIFT+ERROR only", p["pin_graded"], 5)
|
||||
check("a DRIFT row alone exits 1", rows[f]["pin_rc"], 1)
|
||||
|
||||
rc_p, p_p = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, f, guard=True,
|
||||
axis="pin")
|
||||
check("--axis pin grades the pin alone and still exits 1 on DRIFT",
|
||||
(rc_p, p_p["rows"][0]["pin"]), (1, "DRIFT"))
|
||||
check("--axis pin sets no index verdict on the row",
|
||||
"verdict" in p_p["rows"][0], False)
|
||||
check("--axis pin counts no index rows", p_p["counts"], {})
|
||||
rc_i, p_i = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, c, guard=True,
|
||||
axis="index")
|
||||
check("--axis index reads the same commit STALE on its own axis",
|
||||
(rc_i, p_i["rows"][0]["verdict"], "pin" in p_i["rows"][0]),
|
||||
(1, "STALE", False))
|
||||
|
||||
rc_g, p_g = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD", guard=True)
|
||||
check("--guard on a clean head exits 0", rc_g, 0)
|
||||
check("--guard reports exactly one commit", len(p_g["rows"]), 1)
|
||||
check("--guard's one row is clean on BOTH axes",
|
||||
(p_g["rows"][0]["verdict"], p_g["rows"][0]["pin"]), ("PASS", "PASS"))
|
||||
rc_s, _ = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, c, guard=True)
|
||||
check("--guard on the stale commit exits 1", rc_s, 1)
|
||||
rc_f, _ = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, f, guard=True)
|
||||
check("--guard on the drift commit exits 1", rc_f, 1)
|
||||
|
||||
# Default range: begin where the gate does. B introduced the index, so
|
||||
# the default table starts at B's parent and A is counted, not listed.
|
||||
rc_a, p_a = audit(repo, QUEUE_DEFAULT, INDEX_DEFAULT, None, "HEAD")
|
||||
check("the default range begins at the commit that introduced the index",
|
||||
(p_a["auto_from"], len(p_a["rows"])), (True, 3))
|
||||
(p_a["auto_from"], len(p_a["rows"])), (True, 5))
|
||||
check("pre-gate commits are counted, not silently dropped", p_a["pre_gate"], 1)
|
||||
check("the default range still grades the stale commit", rc_a, 1)
|
||||
|
||||
@@ -491,8 +685,9 @@ def selftest():
|
||||
report(p_j, stream=buf)
|
||||
with open(json_path, encoding="utf-8") as fh:
|
||||
loaded = json.load(fh)
|
||||
check("--json round-trips the same 4 rows", len(loaded["rows"]), 4)
|
||||
check("--json round-trips the same 6 rows", len(loaded["rows"]), 6)
|
||||
check("the report names the stale commit by short sha", c[:7] in buf.getvalue(), True)
|
||||
check("the report names the drift commit on its own line", "drift: " + f[:7] in buf.getvalue(), True)
|
||||
check("a report with a fault never prints a clean verdict",
|
||||
"every graded commit" in buf.getvalue(), False)
|
||||
finally:
|
||||
@@ -524,6 +719,9 @@ def main(argv=None):
|
||||
"commits as UNGRADABLE rows too")
|
||||
ap.add_argument("--guard", action="store_true",
|
||||
help="grade only the newest queue-touching commit")
|
||||
ap.add_argument("--axis", choices=("both", "index", "pin"), default="both",
|
||||
help="which axis to grade: the rendered index, the queue's "
|
||||
"own PIN_COUNTS, or both (default both)")
|
||||
ap.add_argument("--timeout", type=int, default=30,
|
||||
help="seconds per commit's --check (default 30)")
|
||||
ap.add_argument("--json", metavar="PATH", help="write the rows here")
|
||||
@@ -533,7 +731,7 @@ def main(argv=None):
|
||||
return selftest()
|
||||
rc, payload = audit(args.repo, args.queue, args.index,
|
||||
args.rev_from, args.rev_to, args.guard, args.timeout,
|
||||
args.all_history)
|
||||
args.all_history, args.axis)
|
||||
report(payload)
|
||||
if args.json:
|
||||
try:
|
||||
|
||||
Reference in new issue
Block a user