Files
troupe-standup/wizard-work/volt/q137-open-set/open_set.py
T

733 lines
29 KiB
Python

#!/usr/bin/env python3
"""Q137 -- the work queue's declared open set, derived and then measured.
Two questions, kept apart on purpose:
DECLARED which ids does `WORK-QUEUE.md` itself leave open? That is a
textual fact: the last declaration for an id, and whether that
declaration's status word opens or closes it. `--check`
pins the answer so the set cannot drift unnoticed.
MEASURED for each declared-open id, is the defect its record describes
still present at HEAD? Each probe is a named function that
reads or runs the thing the record cites and returns a state
plus the evidence it read. `--closures` pins those states.
A repaired item with no closure record is the defect this file exists for:
the queue says "OPEN", a newcomer takes it, and the work is already done.
The probe table is the measurement; the pin is what makes the next run a
regression test rather than a fresh opinion.
States
REPAIRED the cited defect is gone at HEAD, measured by the probe
PARTIAL one part of the record is satisfied, another is not
STILL-OPEN the cited defect is still there at HEAD
UNMEASURED no probe here can settle it (network, archive, judgement)
Exit codes (tools/ contract)
0 graded clean / pin matches
1 a fault: drift from the pin, or a probe moved
2 could not grade (missing file, unreadable queue, probe crashed)
Stdlib only. Nothing here writes to the tree.
"""
import argparse
import ast
import importlib.util
import io
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
from contextlib import redirect_stdout
from pathlib import Path
QUEUE = "WORK-QUEUE.md"
# ---------------------------------------------------------------- declarations
EM = "\u2014"
SEP = re.compile(r"^[\u2014\u2013-]{1,2}\s*")
DASHES = (" " + EM + " ", " -- ", " - ")
OPEN_WORDS = {"OPEN", "OPENED", "REOPENED"}
CLOSED_WORDS = {"DONE", "CLOSED", "VERIFIED", "REPAIRED", "ACCEPTED",
"CORRECTION", "ERRATUM", "WITHDRAWN", "RETIRED",
"ANSWERED", "RESOLVED"}
CLOSED_IN_PLACE = re.compile(r"OPENED and REPAIRED|OPENED and CLOSED|"
r"opened and repaired in the same tick")
HEADING = re.compile(r"^(#{2,})\s*Q(\d+)\b([\s\u2014\u2013-].*)?$")
RECORD = re.compile(r"^\*\*Q(\d+)\b\s*[\u2014\u2013-]{1,2}\s*(.*)$")
def classify(tail):
"""State word from the text after the id, or 'OTHER' / 'SECTION'."""
t = tail.strip().lstrip("*").strip()
t = SEP.sub("", t).strip()
word = t.split()[0].strip(".,:;()") if t.split() else ""
word = word.strip("*").strip(".,:;()")
w = word.upper()
if w in OPEN_WORDS:
if CLOSED_IN_PLACE.search(t[:80]):
return "CLOSED"
return "OPEN"
if w in CLOSED_WORDS:
return "CLOSED"
return "OTHER"
def parse(text):
"""-> (decls, state): decls is a list of dicts in line order."""
decls = []
for n, line in enumerate(text.splitlines(), 1):
m = RECORD.match(line)
kind = "record"
if not m:
m = HEADING.match(line)
kind = "heading"
if not m:
continue
qid = int(m.group(2) if kind == "heading" else m.group(1))
tail = m.group(3) if kind == "heading" else m.group(2)
state = "SECTION" if kind == "heading" else classify(tail)
decls.append({
"qid": qid,
"line": n,
"kind": kind,
"state": state,
"word": (tail.lstrip("*").strip() or "")[:100],
})
state = {}
for d in decls:
if d["kind"] == "record":
state[d["qid"]] = d # last record wins, line order
elif d["qid"] not in state:
state[d["qid"]] = {"qid": d["qid"], "line": d["line"],
"kind": "heading", "state": "SECTION",
"word": d["word"]}
return decls, state
def open_ids(state):
return sorted(q for q, d in state.items() if d["state"] == "OPEN")
def opened_ids(decls):
"""Every id that carries at least one OPEN record, closed or not."""
return sorted({d["qid"] for d in decls
if d["kind"] == "record" and d["state"] == "OPEN"})
def counts(decls, state):
heads = [d for d in decls if d["kind"] == "heading"]
recs = [d for d in decls if d["kind"] == "record"]
return {
"headings": len(heads),
"heading_ids": len({d["qid"] for d in heads}),
"records": len(recs),
"record_ids": len({d["qid"] for d in recs}),
"ids": len(state),
"open": len(open_ids(state)),
"opened": len(opened_ids(decls)),
"record_only": len({d["qid"] for d in recs} - {d["qid"] for d in heads}),
}
# ---------------------------------------------------------------- the probes
#
# Each probe takes the repo root and returns (state, evidence). Evidence is
# the thing a reader can check: a file:line, an rc, a count, a sentence.
def read(repo, rel):
return (repo / rel).read_text(encoding="utf-8", errors="replace")
def run(argv, cwd, timeout=120):
try:
p = subprocess.run(argv, cwd=str(cwd), capture_output=True, text=True,
timeout=timeout)
return p.returncode, (p.stdout or "") + (p.stderr or "")
except (OSError, subprocess.TimeoutExpired) as exc:
return None, str(exc)
def _load(path, name):
spec = importlib.util.spec_from_file_location(name, str(path))
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
def probe_q100(repo):
"""Q100: import_resolve_check's TEST_NAME must select `_test_x.py`."""
src = read(repo, "tools/import_resolve_check.py")
pat = None
for node in ast.walk(ast.parse(src)):
if isinstance(node, ast.Assign):
for t in node.targets:
if isinstance(t, ast.Name) and t.id == "TEST_NAME":
call = node.value
if isinstance(call, ast.Call) and call.args:
try:
pat = ast.literal_eval(call.args[0])
except ValueError:
pat = None
if pat is None:
return "STILL-OPEN", "TEST_NAME not found as a literal in import_resolve_check.py"
rx = re.compile(pat)
samples = ["wizard-work/forge/_test_roundtrip.py",
"wizard-work/forge/test_log.txt",
"wizard-work/forge/_test_roundtrip_refactored.py"]
hit = [s for s in samples[:1] + samples[2:] if rx.search(s)]
return ("REPAIRED" if len(hit) == 2 else "STILL-OPEN",
"TEST_NAME=%r selects %d/2 underscore test files" % (pat, len(hit)))
def probe_q101(repo):
"""Q101: ledger/census.py must at least parse."""
path = repo / "wizard-work/ledger/census.py"
try:
compile(path.read_text(encoding="utf-8"), str(path), "exec")
except (OSError, SyntaxError) as exc:
return "STILL-OPEN", "census.py will not compile: %s" % (exc,)
return "REPAIRED", "census.py compiles (compile() at %s)" % path.name
def probe_q102(repo):
"""Q102: three outcomes must be three channels."""
mod = _load(repo / "wizard-work/beam/majority_element.py", "q137_maj")
try:
got = mod.majority([None, None, 1])
none_majority = got is None
except Exception: # noqa: BLE001
none_majority = False
raised = False
try:
mod.majority([1, 2, 3])
except Exception as exc: # noqa: BLE001
raised = type(exc).__name__ == "NoMajority"
return ("REPAIRED" if none_majority and raised else "STILL-OPEN",
"majority([None,None,1])->None=%s; majority([1,2,3]) raises "
"NoMajority=%s" % (none_majority, raised))
def probe_q103(repo):
"""Q103: queue_index must see ids of any width."""
rc, out = run([sys.executable, "tools/queue_index.py",
"--check", "docs/queue-index.md"], repo)
ok = rc == 0
return ("REPAIRED" if ok else "STILL-OPEN",
"queue_index --check docs/queue-index.md rc=%s; %s"
% (rc, out.strip().splitlines()[-1][:120] if out.strip() else ""))
def probe_q104(repo):
"""Q104: undecodable JSONL must be a typed exit 2, no traceback."""
tmp = Path(tempfile.mkdtemp(prefix="q137-q104-"))
try:
bad = tmp / "bad.jsonl"
bad.write_bytes(b'{"a": 1}\n\xff\xfe not utf-8\n')
rc, out = run([sys.executable, "wizard-work/beam/jsonl_validate.py",
str(bad)], repo)
finally:
shutil.rmtree(tmp, ignore_errors=True)
typed = rc == 2 and "Traceback" not in out
return ("REPAIRED" if typed else "STILL-OPEN",
"undecodable input rc=%s traceback=%s"
% (rc, "Traceback" in out))
def probe_q105(repo):
"""Q105/Q128: the beam gate must collect both test styles."""
txt = read(repo, "wizard-work/beam/CHECK")
ok = "run_lane_tests.py" in txt
return ("REPAIRED" if ok else "STILL-OPEN",
"beam/CHECK runs run_lane_tests.py=%s (%d lines)"
% (ok, len(txt.splitlines())))
def probe_q106(repo):
"""Q106: the dated sol gate must not pin an absolute host root."""
txt = read(repo, "wizard-work/sol/CHECK-2026-09-28")
n = txt.count("/root")
return ("REPAIRED" if n == 0 else "STILL-OPEN",
"CHECK-2026-09-28 /root occurrences=%d" % n)
def probe_q107(repo):
"""Q107: --dir must not exit the number of invalid files."""
tmp = Path(tempfile.mkdtemp(prefix="q137-q107-"))
try:
(tmp / "ok.json").write_text('{"a": 1}', encoding="utf-8")
(tmp / "bad1.json").write_text("{nope", encoding="utf-8")
(tmp / "bad2.json").write_text("{nope", encoding="utf-8")
(tmp / "sub").mkdir()
(tmp / "sub" / "deep.json").write_text('{"b": 2}', encoding="utf-8")
rc, out = run([sys.executable, "wizard-work/spark/json-validator.py",
str(tmp)], repo)
finally:
shutil.rmtree(tmp, ignore_errors=True)
valid = sum(1 for ln in out.splitlines() if ln.startswith("VALID:"))
invalid = sum(1 for ln in out.splitlines() if ln.startswith("INVALID:"))
ok = rc == 1 and (valid, invalid) == (2, 2)
return ("REPAIRED" if ok else "STILL-OPEN",
"dir with 2 invalid + 2 valid (one nested): rc=%s valid=%d invalid=%d"
% (rc, valid, invalid))
def probe_q109(repo):
"""Q109: snippet.py's tool check must measure, not assume."""
txt = read(repo, "wizard-work/spark/snippet.py")
has_fn = "def check_tools" in txt
returns_measurement = "available, error = check_tools" in txt
graded = (repo / "wizard-work/spark/test_env_verifiers.py").is_file()
ok = has_fn and returns_measurement and graded
return ("REPAIRED" if ok else "STILL-OPEN",
"check_tools=%s, call site=%s, graded by test_env_verifiers=%s"
% (has_fn, returns_measurement, graded))
def probe_q110(repo):
"""Q110: the QUICKSTART's clone step must point at a live repository."""
txt = read(repo, "wizard-work/quill/QUICKSTART.md")
clones = re.findall(r"^git clone (\S+)", txt, re.M)
live = [u for u in clones if "forge.alexanderwhitestone.com" in u]
ok = len(clones) == 1 and len(live) == 1
return ("REPAIRED" if ok else "STILL-OPEN",
"%d git clone step(s): %s" % (len(clones), clones[:3]))
def probe_q111(repo):
"""Q111: zero-length intervals must not skew the optimum."""
mod = _load(repo / "wizard-work/beam/weighted_interval_scheduling.py",
"q137_wis")
fn = getattr(mod, "weighted_interval_scheduling")
def ok_pair(sel):
for i in range(len(sel)):
for j in range(i + 1, len(sel)):
a, b = sel[i], sel[j]
if min(a[1], b[1]) - max(a[0], b[0]) > 0:
return False
return True
bad = 0
trials = 0
import random
rng = random.Random(1371)
for _ in range(60):
n_try = rng.randint(1, 4)
iv = [[rng.randint(0, 3), rng.randint(0, 3), rng.randint(1, 4)]
for _ in range(n_try)]
iv = [[min(a, b), max(a, b), w] for a, b, w in iv]
got = fn(iv)
best = 0
m = len(iv)
for mask in range(1 << m):
sel = [iv[i] for i in range(m) if mask >> i & 1]
if ok_pair(sel) and sum(x[2] for x in sel) > best:
best = sum(x[2] for x in sel)
trials += 1
if got != best:
bad += 1
return ("REPAIRED" if bad == 0 and trials else "STILL-OPEN",
"zero-length included: %d mismatches in %d exhaustive trials"
% (bad, trials))
def probe_q112(repo):
"""Q112: the census must see every heading, and every record."""
rc, out = run([sys.executable,
"wizard-work/ledger/workqueue-census-v2/CHECK"], repo)
line = ""
for ln in out.splitlines():
if "distinct labels" in ln:
line = ln.strip()
if rc != 0 or not line:
return "STILL-OPEN", "census CHECK rc=%s, no summary line" % rc
m = re.search(r"\((\d+) rows, (\d+) distinct labels", line)
text = read(repo, QUEUE)
decls, state = parse(text)
c = counts(decls, state)
rows, labels = (int(m.group(1)), int(m.group(2))) if m else (0, 0)
covered = rows >= c["headings"] and labels >= c["heading_ids"]
return ("REPAIRED" if covered else "STILL-OPEN",
"census %d rows/%d labels vs %d headings/%d heading ids; "
"%d record-only ids are enumerated by the extractor, not as rows"
% (rows, labels, c["headings"], c["heading_ids"], c["record_only"]))
def probe_q113(repo):
"""Q113: the roster gate must fail on a seeded mutation."""
led = "wizard-work/ledger"
tmp = Path(tempfile.mkdtemp(prefix="q137-q113-"))
try:
dst = tmp / led
dst.mkdir(parents=True)
for name in ("wizards.csv", "wizards.manifest.json"):
shutil.copy2(repo / led / name, dst / name)
man = json.loads((dst / "wizards.manifest.json").read_text(encoding="utf-8"))
src_rel = man.get("source", "")
if src_rel:
(tmp / src_rel).parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(repo / src_rel, tmp / src_rel)
csv = (dst / "wizards.csv").read_text(encoding="utf-8")
(dst / "wizards.csv").write_text(csv.replace("Timmy", "GARBAGE NAME"),
encoding="utf-8")
bad_rc, _ = run([sys.executable, led + "/verify_wizards.py",
"--root", str(tmp)], repo)
good_rc, _ = run([sys.executable, led + "/verify_wizards.py",
"--root", str(repo)], repo)
finally:
shutil.rmtree(tmp, ignore_errors=True)
ok = bad_rc == 1 and good_rc == 0
return ("REPAIRED" if ok else "STILL-OPEN",
"seeded mutation rc=%s, clean tree rc=%s" % (bad_rc, good_rc))
def probe_q115(repo):
"""Q115: an unreadable folder must not read as a mass deletion."""
txt = Path("/root/.hermes/scripts/troupe_pipe_monitor.py").read_text(
encoding="utf-8", errors="replace")
ok = "UNREADABLE" in txt and "carried" in txt
return ("REPAIRED" if ok else "UNMEASURED",
"monitor separates UNREADABLE=%s and carries the listing=%s"
% ("UNREADABLE" in txt, "carried" in txt))
def probe_q117(repo):
"""Q117: an invented probe value must be data, not a recovery."""
txt = read(repo, "wizard-work/zeph/q60_enforce.py")
ok = "KNOWN_PROBE" in txt and txt.count("KNOWN_PROBE") >= 3
return ("REPAIRED" if ok else "STILL-OPEN",
"q60_enforce.py KNOWN_PROBE occurrences=%d"
% txt.count("KNOWN_PROBE"))
def probe_q118(repo):
"""Q118: a failed listing must be retried and report attempts/last."""
txt = Path("/root/.hermes/scripts/troupe_pipe_monitor.py").read_text(
encoding="utf-8", errors="replace")
retry = "for attempt in range(1, tries + 1)" in txt
fields = "attempts=" in txt and "last=" in txt
return ("REPAIRED" if retry and fields else "STILL-OPEN",
"retry loop=%s, attempts=/last= fields=%s" % (retry, fields))
def probe_q119(repo):
"""Q119: an unmatched demand must not be silently 'info'."""
txt = read(repo, "wizard-work/sol/classify.py")
ok = "unclassified" in txt and "Fallback" in txt
return ("REPAIRED" if ok else "STILL-OPEN",
"classify.py names an unclassified fallback in its header=%s" % ok)
def probe_q120(repo):
"""Q120: the env check must import real dependencies and report versions."""
txt = read(repo, "wizard-work/spark/python-env-check.py")
decl = "DEPENDENCIES" in txt
called = len(re.findall(r"check_package\(", txt)) >= 2
return ("REPAIRED" if decl and called else "STILL-OPEN",
"DEPENDENCIES table=%s, check_package call sites=%d"
% (decl, len(re.findall(r"check_package\(", txt))))
def probe_q121(repo):
"""Q121: fixture absence must be asserted per value, not over a join."""
txt = read(repo, "wizard-work/zeph/q60_enforce.py")
per_value = "for value in KNOWN_FIXTURE.values()" in txt or \
"KNOWN_FIXTURE.values()" in txt
mutant = "two_entry" in txt
ok = per_value and mutant
return ("REPAIRED" if ok else "STILL-OPEN",
"per-value absence=%s, two-entry mutant cell=%s"
% (per_value, mutant))
def probe_q122(repo):
"""Q122: the census CHECK must not gate on a wall clock."""
rc, out = run([sys.executable,
"wizard-work/ledger/workqueue-census-v2/CHECK"], repo)
skipped = "SKIP" in out.upper()
return ("REPAIRED" if rc == 0 else "STILL-OPEN",
"census CHECK rc=%s, clock leg reads %s"
% (rc, "SKIP" if skipped else "graded"))
def unmade(reason):
def probe(repo):
return "UNMEASURED", reason
return probe
PROBES = {
100: probe_q100, 101: probe_q101, 102: probe_q102, 103: probe_q103,
104: probe_q104, 105: probe_q105, 106: probe_q106, 107: probe_q107,
109: probe_q109, 110: probe_q110, 111: probe_q111, 112: probe_q112,
113: probe_q113, 115: probe_q115, 117: probe_q117, 118: probe_q118,
119: probe_q119, 120: probe_q120, 121: probe_q121, 122: probe_q122,
51: unmade("gate-wide probe window: needs the live gate, not a file read"),
52: unmade("archive attribution: needs pipeline logs this tree does not hold"),
53: unmade("digest domain + state.db: needs the live database"),
65: unmade("population claim over 87 verifiers; the Q87 gate owns it now"),
114: unmade("free-tier rows contradict six provider endpoints: network"),
116: unmade("OpenTrain validThrough is per-render: network, time-lapsed"),
}
PIN_OPEN = []
PIN_OPENED = [51, 52, 53, 65, 100, 101, 102, 103, 104, 105, 106, 107, 109,
110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]
# Q147 (2026-11-01): the record count, the id count and the record-only count
# move with any append to WORK-QUEUE.md -- this one added Q147, which is a
# record and an id and a record-only id, and no heading. Re-taken at the commit
# that carries the record: 106 -> 107 records, 137 -> 138 ids, 72 -> 73
# record_ids, 51 -> 52 record_only; headings, heading_ids, open and opened
# unchanged (97 / 86 / 0 / 26), so no id changed its state.
# Q148 (2026-11-01): the same move for the same reason -- one appended record,
# one appended id, no heading: 107 -> 108 records, 138 -> 139 ids, 73 -> 74
# record_ids, 52 -> 53 record_only; headings, heading_ids, open and opened
# unchanged (97 / 86 / 0 / 26), so again no id changed its state.
PIN_COUNTS = {"headings": 97, "heading_ids": 86, "records": 108,
"record_ids": 74, "ids": 139, "open": 0, "opened": 26,
"record_only": 53}
PIN_STATE = {
100: "REPAIRED", 101: "REPAIRED", 102: "REPAIRED", 103: "REPAIRED",
104: "REPAIRED", 105: "REPAIRED", 106: "REPAIRED", 107: "REPAIRED",
109: "REPAIRED", 110: "REPAIRED", 111: "REPAIRED", 112: "REPAIRED",
113: "REPAIRED", 115: "REPAIRED", 117: "REPAIRED", 118: "REPAIRED",
119: "REPAIRED", 120: "REPAIRED", 121: "REPAIRED", 122: "REPAIRED",
51: "UNMEASURED", 52: "UNMEASURED", 53: "UNMEASURED", 65: "UNMEASURED",
114: "UNMEASURED", 116: "UNMEASURED",
}
# ---------------------------------------------------------------- the selftest
FIXTURES = [
("open-then-done",
"**Q5 \u2014 OPEN (a).** body\n**Q5 \u2014 DONE at `abc`.**\n", 5, "CLOSED"),
("opened-and-repaired-in-place",
"**Q7 \u2014 OPENED and REPAIRED in the same tick (volt).** body\n",
7, "CLOSED"),
("open-with-no-close",
"**Q9 \u2014 OPEN (pipe).** body\n", 9, "OPEN"),
("heading-only",
"## Q11 \u2014 A section title\nbody\n", 11, "SECTION"),
("heading-then-record",
"## Q13 \u2014 A section title\nbody\n**Q13 \u2014 DONE.**\n", 13, "CLOSED"),
("bold-not-a-declaration",
"**Q15 measured.** prose, no dash\n", 15, None),
("width-boundary",
"**Q1000 \u2014 OPEN (x).**\n**Q100 \u2014 DONE.**\n", 100, "CLOSED"),
("reopened-after-done",
"**Q17 \u2014 DONE.**\n**Q17 \u2014 REOPENED (pipe).**\n", 17, "OPEN"),
]
def graded(repo, text):
"""mode_check's rc with its output swallowed -- for the mutant cells."""
buf = io.StringIO()
try:
with redirect_stdout(buf):
return mode_check(repo, text)
finally:
buf.close()
def selftest():
cells = []
for name, text, qid, want in FIXTURES:
_, state = parse(text)
got = state.get(qid, {}).get("state")
cells.append((name, got == want, "got=%r want=%r" % (got, want)))
repo, text = load_repo(str(Path(__file__).resolve().parents[3]))
if repo is not None and text is not None:
once = parse(text)
cells.append(("parse-is-deterministic", once == parse(text), ""))
cells.append(("gate-green-control", graded(repo, text) == 0, "rc"))
cells.append(("gate-new-open-id",
graded(repo, text + "\n**Q999 \u2014 OPEN (x).**\n") == 1,
"rc"))
cells.append(("gate-done-turned-open",
graded(repo, text.replace("**Q117 \u2014 DONE",
"**Q117 \u2014 OPEN", 1)) == 1,
"rc"))
cells.append(("gate-heading-dropped",
graded(repo, re.sub(r"(?m)^## Q5 \u2014.*\n", "", text,
count=1)) == 1, "rc"))
bad = [c for c in cells if not c[1]]
for name, ok, detail in cells:
print("%-28s %s %s" % (name, "PASS" if ok else "FAIL", detail))
print("SELFTEST %s (%d cells, %d failed)"
% ("OK" if not bad else "FAILED", len(cells), len(bad)))
return 0 if not bad else 1
def parse_text_roundtrip():
return "**Q1 \u2014 OPEN (x).**\n"
# ---------------------------------------------------------------- the modes
def load_repo(arg):
root = Path(arg).resolve()
q = root / QUEUE
if not q.is_file():
print("cannot grade: no %s under %s" % (QUEUE, root))
return None, None
return root, q.read_text(encoding="utf-8")
def mode_check(repo, text):
decls, state = parse(text)
c = counts(decls, state)
got = open_ids(state)
print("queue: %d lines, %d headings/%d ids, %d records/%d ids, "
"%d record-only ids" % (len(text.splitlines()), c["headings"],
c["heading_ids"], c["records"],
c["record_ids"], c["record_only"]))
print("declared open: %d; ids ever opened: %d" % (len(got), c["opened"]))
faults = []
for key in sorted(PIN_COUNTS):
if c[key] != PIN_COUNTS[key]:
faults.append("count %s moved %d -> %d" % (key, PIN_COUNTS[key], c[key]))
if got != PIN_OPEN:
added = [q for q in got if q not in PIN_OPEN]
gone = [q for q in PIN_OPEN if q not in got]
faults.append("open set moved: +%s -%s" % (added, gone))
if opened_ids(decls) != PIN_OPENED:
faults.append("the set of ever-opened ids moved")
for f in faults:
print("FAIL %s" % f)
print("CHECK %s" % ("OK: 0 open, %d ever opened, counts match the pin"
% len(PIN_OPENED)
if not faults else "%d fault(s)" % len(faults)))
return 0 if not faults else 1
def mode_order(repo, text):
"""Line order must be commit order, or 'last record' is not 'latest'."""
decls, _ = parse(text)
recs = [d for d in decls if d["kind"] == "record"]
rc, out = run(["git", "blame", "--porcelain", "--", QUEUE], repo)
if rc != 0 or not out:
print("cannot grade order: git blame failed")
return 2
by_line = {}
commit = None
for ln in out.splitlines():
m = re.match(r"^([0-9a-f]{40}) \d+ (\d+)(?: \d+)?$", ln)
if m:
commit = m.group(1)
by_line[int(m.group(2))] = commit
times = {}
stamps = []
pending = 0
for d in recs:
c = by_line.get(d["line"])
if c is None:
print("cannot grade order: no blame entry for line %d" % d["line"])
return 2
if set(c) == {"0"}:
pending += 1 # appended, not yet committed
continue
if c not in times:
trc, tout = run(["git", "show", "-s", "--format=%ct", c], repo)
if trc != 0:
print("cannot grade order: no timestamp for %s" % c[:12])
return 2
times[c] = int(tout.strip())
stamps.append((d["line"], times[c], c))
if not stamps:
print("cannot grade order: no committed record lines")
return 2
bad = []
for (l1, t1, c1), (l2, t2, c2) in zip(stamps, stamps[1:]):
if t2 < t1:
bad.append("line %d (%s) is newer than line %d (%s)"
% (l2, c2[:8], l1, c1[:8]))
print("record order: %d commits over %d committed records (%d pending "
"commit); %d inversion(s)"
% (len(times), len(stamps), pending, len(bad)))
for b in bad[:5]:
print("FAIL %s" % b)
print("CHECK %s" % ("OK: line order is commit order, so 'last record' is "
"'latest'" if not bad else "%d inversion(s)" % len(bad)))
return 0 if not bad else 1
def mode_table(repo, text):
decls, state = parse(text)
for q in opened_ids(decls):
d = state[q]
print("Q%-4d %-6s :%-6d %s" % (q, d["state"], d["line"], d["word"][:90]))
return 0
def mode_closures(repo, text):
decls, state = parse(text)
want = opened_ids(decls)
if want != PIN_OPENED:
print("cannot grade closures: the ever-opened set moved, run --check")
return 2
faults = []
rows = []
for q in want:
probe = PROBES.get(q)
if probe is None:
rows.append((q, "UNMEASURED", "no probe registered"))
continue
try:
got, ev = probe(repo)
except Exception as exc: # noqa: BLE001
print("Q%-4d UNGRADABLE %s: %s" % (q, type(exc).__name__, exc))
return 2
rows.append((q, got, ev))
tally = {}
for q, got, ev in rows:
tally[got] = tally.get(got, 0) + 1
pin = PIN_STATE.get(q)
mark = " " if got == pin else "!"
print("%sQ%-4d %-10s %s" % (mark, q, got, ev))
if got != pin:
faults.append("Q%d moved %s -> %s" % (q, pin, got))
print("closure tally: %s" % ", ".join("%s %d" % kv for kv in sorted(tally.items())))
for f in faults:
print("FAIL %s -- re-pin with the commit that moved it" % f)
print("CHECK %s" % ("OK: %d ever-opened ids, %d measured, states match the pin"
% (len(want), len(rows) - tally.get("UNMEASURED", 0))
if not faults else "%d fault(s)" % len(faults)))
return 0 if not faults else 1
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
ap.add_argument("--repo", default=str(Path(__file__).resolve().parents[3]))
ap.add_argument("--check", action="store_true", help="grade the declared set")
ap.add_argument("--closures", action="store_true", help="grade measured states")
ap.add_argument("--order", action="store_true", help="line order vs commit order")
ap.add_argument("--table", action="store_true", help="print the open set")
ap.add_argument("--selftest", action="store_true", help="parser cells")
args = ap.parse_args(argv)
if args.selftest:
return selftest()
repo, text = load_repo(args.repo)
if repo is None:
return 2
if args.table:
return mode_table(repo, text)
if args.order:
return mode_order(repo, text)
if args.closures:
return mode_closures(repo, text)
return mode_check(repo, text)
if __name__ == "__main__":
sys.exit(main())