volt: path_claim_check — grade every in-namespace path the archive names against the tree at a revision

This commit is contained in:
volt committed 2026-10-20 22:17:43 +00:00
1 parent 4d18f779b0
commit 5f892d7311
3 files changed
+519

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
# path_claim baseline -- the MISSING paths this archive already carried when
# path_claim_check.py first censused it. One row: <path>\t<CLASS>\t<reason>.
# Regenerate the census (not the reasons): path_claim_check.py --repo . --context
# A row is a stated reading, not an exemption: --fail-on-missing fails on any
# MISSING path this ledger does not name, and every row here is printed with its
# class on every run, so a reader sees the exceptions instead of trusting that
# there are none. A row whose path later appears in the tree is reported CLEARED
# and can be deleted; a row the census no longer produces is reported DEAD.
# Classes: PLANTED (a file planted in a clone to measure a gate), NOT_ADDED (the
# prose states the file was never added on any ref), RENAMED (the artifact exists
# under a later name), SHORT_NAME (the prose cites an abbreviated form of a real
# artifact), FOREIGN_TREE (a path relative to another project's tree).
career-lane/ats-gate-census-draft.md RENAMED history under that name at f29425a..e1426e9; the artifact at HEAD is career-lane/ats-gate-census.md
inbox/pending.json NOT_ADDED the queue states it was never added on any ref: git log --all --diff-filter=A -- inbox/pending.json -> no output
recoveries/summary-fidelity-full.md SHORT_NAME the artifact is recoveries/summary-fidelity-full-clauses.md; the queue cites the short form
checkins/2026-10-19-volt-02.md PLANTED planted in a clone of 3890c12 to measure the gate hole; the citing sentence says so
checkins/2026-10-02-volt-b.md PLANTED planted in a clone of e98a34b as the backdated-name example; the citing sentence says so
docs/release-engine-spec.md FOREIGN_TREE relative to another project's tree (src/, tests/): neither is tracked here
replies/2026-10-19-to-volt-99.md PLANTED planted in a clone of 5defac9 to red the reply leg; the citing sentence says so
1 # path_claim baseline -- the MISSING paths this archive already carried when
2 # path_claim_check.py first censused it. One row: <path>\t<CLASS>\t<reason>.
3 # Regenerate the census (not the reasons): path_claim_check.py --repo . --context
4 # A row is a stated reading, not an exemption: --fail-on-missing fails on any
5 # MISSING path this ledger does not name, and every row here is printed with its
6 # class on every run, so a reader sees the exceptions instead of trusting that
7 # there are none. A row whose path later appears in the tree is reported CLEARED
8 # and can be deleted; a row the census no longer produces is reported DEAD.
9 # Classes: PLANTED (a file planted in a clone to measure a gate), NOT_ADDED (the
10 # prose states the file was never added on any ref), RENAMED (the artifact exists
11 # under a later name), SHORT_NAME (the prose cites an abbreviated form of a real
12 # artifact), FOREIGN_TREE (a path relative to another project's tree).
13 career-lane/ats-gate-census-draft.md RENAMED history under that name at f29425a..e1426e9; the artifact at HEAD is career-lane/ats-gate-census.md
14 inbox/pending.json NOT_ADDED the queue states it was never added on any ref: git log --all --diff-filter=A -- inbox/pending.json -> no output
15 recoveries/summary-fidelity-full.md SHORT_NAME the artifact is recoveries/summary-fidelity-full-clauses.md; the queue cites the short form
16 checkins/2026-10-19-volt-02.md PLANTED planted in a clone of 3890c12 to measure the gate hole; the citing sentence says so
17 checkins/2026-10-02-volt-b.md PLANTED planted in a clone of e98a34b as the backdated-name example; the citing sentence says so
18 docs/release-engine-spec.md FOREIGN_TREE relative to another project's tree (src/, tests/): neither is tracked here
19 replies/2026-10-19-to-volt-99.md PLANTED planted in a clone of 5defac9 to red the reply leg; the citing sentence says so
+495
View File
@@ -0,0 +1,495 @@
#!/usr/bin/env python3
"""path_claim_check.py -- does every in-namespace path this archive names exist
at this revision?
WHY THIS EXISTS
`citation_resolve_check.py` grades `<path>:<line>` citations: it can resolve
them because a line number makes the claim decidable. A path named WITHOUT a
line -- "`recoveries/foo.md` (10,232 B)", "the record `docs/bar.md`" -- is the
same kind of claim with one fewer field, and nothing here graded it. The
defect is on the record: checkins/2026-10-19-volt.md records a report that
"named `check.sh` and `files.sha256`" and never wrote them, and the ledger's
rule is that a reader must be able to go and look. A path a reader cannot
open is a citation that fails the only test a stranger can run.
The measurement that made this tool possible also bounded it: over the whole
corpus at HEAD, 1,024 distinct path tokens appear, and only 317 of them are
even candidates for "this archive holds that file". The other 707 begin with
a component this repo does not track -- scratch trees (`after/verify.txt`),
host artifacts (`/root/crucible/...`), other repositories, served routes and
URLs. Grading those as missing would be 707 false defects, so they are COUNTED
and never graded. What is left is small enough to read: 8 missing paths, each
of which is in the reviewed ledger with the class that explains it.
WHAT IT MEASURES -- one verdict per DISTINCT path (unit: distinct paths)
EXISTS the path is in the tree at --rev.
MISSING the path's first component IS a tracked top-level entry, so the
prose is naming something inside this repository, and the tree at
--rev does not hold it. This is the graded class.
EXTERNAL the first component is not tracked here (scratch tree, host path,
URL, other repo). OUT OF SCOPE: the path may be perfectly true and
this tool cannot see it. Counted, never graded -- the same
vocabulary `citation_resolve_check.py` uses for EXTERNAL_PATH.
TEMPLATE the path carries a placeholder (`YYYY`, `<name>`, `{n}`), so it is
a pattern rather than a claim about one file.
Occurrences are counted beside distinct paths, because one wrong path cited
16 times is one defect and 16 chances for a reader to trip on it.
A FENCED BLOCK IS QUOTED MATERIAL, NOT THIS DOCUMENT'S PROSE -- the rule
`queue_index.py`, `checkin_check.py` and `citation_resolve_check.py` already
apply to headings and citations. A fenced listing that quotes a path is not
the containing document asserting that path exists. Skipped lines are printed
on every run, never silently dropped.
THE BASELINE, AND WHY THE FIRST CENSUS IS ITS LEDGER
`<path>\\t<CLASS>\\t<reason>`, one row per reviewed missing path, class from a
closed set (PLANTED, NOT_ADDED, HOST_ROUTE, RENAMED, SHORT_NAME,
FOREIGN_TREE). A row is a stated reading, not an exemption: `--fail-on-missing`
fails only on a MISSING path the ledger does not name, and every tolerated row
is still PRINTED with its class on every run, so a reader sees the eight
exceptions rather than trusting that there are none. A ledger row whose path
now exists is reported CLEARED, and a row naming a path the census no longer
produces is reported as a dead row -- the ledger cannot rot invisibly.
EXIT CODES
0 the census ran. Without --fail-on-missing a MISSING path is not a fault.
1 --fail-on-missing and at least one MISSING path is not in the baseline, or
names a class the ledger does not tolerate.
2 could not grade: not a git work tree, a revision that does not resolve, a
corpus with no markdown, a baseline that cannot be read or names an
unknown class. Never 0, and never a silent empty census.
USAGE
python3 path_claim_check.py [--repo DIR] [--rev HEAD] [--corpus PATH...]
[--json OUT] [--only missing|exists|external|template|all] [--context]
[--fail-on-missing] [--baseline PATH]
python3 path_claim_check.py --selftest # 17 cells
python3 path_claim_check.py --repo . --fail-on-missing \\
--baseline tools/path_claim_baseline.tsv
"""
import argparse
import collections
import json
import os
import re
import subprocess
import sys
DEFAULT_REPO = "/root/troupe-standup"
# The extensions this archive actually names. A closed list, for the reason
# citation_resolve_check.py gives: an open `\\.[a-z]+` swallows clock times,
# decimals and domain names.
EXT = (r"md|markdown|py|json|txt|js|mjs|cjs|sh|bash|yml|yaml|html|css|csv|tsv|"
r"jsonl|sql|db|sqlite|age|wav|mp3|mp4|gz|tgz|toml|cfg|ini|log|out|bak")
# At least one '/' is REQUIRED: a bare filename ("index.html") is not a claim
# about a location, and the archive names dozens of those for pages it does not
# hold. The trailing guard stops `x.md` from matching the front of `x.md5`.
PATH = re.compile(r"(?P<path>/?(?:[A-Za-z0-9_.@+-]+/)+[A-Za-z0-9_.@+-]+\.(?:%s))"
r"(?![A-Za-z0-9])" % EXT)
FENCE = re.compile(r"^\s*(```|~~~)")
HOST = re.compile(r"^(?:/|~/|\.\./)")
TEMPLATE = re.compile(r"YYYY|<[^>]*>|\{[^}]*\}|NNN")
STRIP = "`*'\"()[]<>,;:."
# The closed class set a baseline row may name. Each is a reason a MISSING path
# is not a live defect; adding one is a deliberate edit to this tuple.
BASELINE_CLASSES = ("PLANTED", "NOT_ADDED", "RENAMED", "SHORT_NAME",
"FOREIGN_TREE")
VERDICTS = ("EXISTS", "MISSING", "EXTERNAL", "TEMPLATE")
class CannotGrade(Exception):
"""An input this tool refuses to grade: the caller turns it into rc 2."""
def git(repo, *args):
return subprocess.run(["git", "-C", repo, *args], capture_output=True)
def precondition(repo, rev):
"""None when `repo` is a git work tree carrying `rev`, else why not.
Every reader below swallows git's status, so an unreadable repo or a
mistyped revision would come back as an empty tree and print a clean census
of nothing -- the silent-pass class selftest_sweep.py's malformed axis
exists to catch.
"""
if not os.path.isdir(repo):
return "cannot read repo %s: no such directory" % repo
p = git(repo, "rev-parse", "--is-inside-work-tree")
if p.returncode != 0 or p.stdout.strip() != b"true":
return "cannot read repo %s: not a git work tree" % repo
q = git(repo, "rev-parse", "--verify", rev + "^{commit}")
if q.returncode != 0:
why = q.stderr.decode("utf-8", "replace").strip().splitlines()
return "cannot read %s at %s: %s" % (repo, rev, why[-1] if why else "git rev-parse failed")
return None
def clean(token):
"""Strip the punctuation a path wears when it sits in prose or in a span."""
p = token.strip(STRIP)
while p.startswith("./"):
p = p[2:].strip(STRIP)
return p
def classify(path, tracked, tops):
if TEMPLATE.search(path):
return "TEMPLATE"
if HOST.match(path) or path.split("/")[0] not in tops:
return "EXTERNAL"
return "EXISTS" if path in tracked else "MISSING"
def fence_mask(text):
"""1-based line numbers that are a fence marker or sit inside a fence."""
out, in_fence = set(), False
for i, raw in enumerate(text.split("\n"), 1):
if FENCE.match(raw):
out.add(i)
in_fence = not in_fence
continue
if in_fence:
out.add(i)
return out
def scan_text(text, where, tracked, tops):
"""Rows for one document -> ({path: row}, counter).
`where` is the document's name, used only to build the first-cite handle.
Line numbers are the document's own, so a row can be read back at --rev.
"""
rows, stats = {}, collections.Counter()
mask = fence_mask(text)
stats["fenced_lines"] = len(mask)
for i, line in enumerate(text.split("\n"), 1):
if i in mask:
continue
for m in PATH.finditer(line):
p = clean(m.group("path"))
if not p:
continue
stats["occurrences"] += 1
row = rows.get(p)
if row is None:
row = rows[p] = {"path": p, "mentions": 0,
"first": "%s:%d" % (where, i),
"context": line.strip()[:180],
"verdict": classify(p, tracked, tops)}
row["mentions"] += 1
return rows, stats
def scan(repo, rev, corpus, store):
all_rows, stats = {}, collections.Counter()
for f in corpus:
text = store.read(f).decode("utf-8", "replace")
rows, st = scan_text(text, f, store.tree, store.tops)
stats.update(st)
for p, row in rows.items():
if p in all_rows:
all_rows[p]["mentions"] += row["mentions"]
else:
all_rows[p] = row
return all_rows, stats
class Store:
def __init__(self, repo, rev):
self.repo, self.rev = repo, rev
self.tree = set(git(repo, "ls-tree", "-r", "--name-only", rev)
.stdout.decode("utf-8", "replace").splitlines())
self.tops = {p.split("/")[0] for p in self.tree}
def read(self, path):
p = git(self.repo, "show", "%s:%s" % (self.rev, path))
if p.returncode != 0:
raise CannotGrade("cannot read %s at %s" % (path, self.rev))
return p.stdout
def load_baseline(path):
"""`<path>\\t<CLASS>\\t<reason>` -> {path: (class, reason)}. Unreadable is rc 2."""
try:
with open(path, "r", encoding="utf-8") as fh:
text = fh.read()
except OSError as exc:
raise CannotGrade("cannot read baseline %s: %s" % (path, exc))
except UnicodeDecodeError as exc:
raise CannotGrade("cannot read baseline %s: not UTF-8: %s" % (path, exc))
out = {}
for n, line in enumerate(text.splitlines(), 1):
s = line.rstrip("\r")
if not s.strip() or s.lstrip().startswith("#"):
continue
parts = s.split("\t")
if len(parts) != 3:
raise CannotGrade("cannot read baseline %s:%d: not "
"<path>\\t<CLASS>\\t<reason>: %r" % (path, n, s))
name, cls, reason = (x.strip() for x in parts)
if not name or not reason:
raise CannotGrade("cannot read baseline %s:%d: empty path or reason: %r"
% (path, n, s))
if cls not in BASELINE_CLASSES:
raise CannotGrade("cannot read baseline %s:%d: unknown class %r "
"(known: %s)" % (path, n, cls, ", ".join(BASELINE_CLASSES)))
out[name] = (cls, reason)
return out
def apply_baseline(rows, ledger):
"""Attach the ledger to each MISSING row; report what moved on both sides."""
missing = {p: r for p, r in rows.items() if r["verdict"] == "MISSING"}
for p, row in missing.items():
if p in ledger:
row["baseline"] = {"known": True, "class": ledger[p][0],
"reason": ledger[p][1], "new": False}
else:
row["baseline"] = {"known": False, "class": None, "reason": None, "new": True}
cleared = sorted(p for p in ledger if p in rows and rows[p]["verdict"] == "EXISTS")
dead = sorted(p for p in ledger if p not in rows)
return {"entries": len(ledger), "matched": sum(1 for r in missing.values()
if r["baseline"]["known"]),
"new": sorted(p for p, r in missing.items() if r["baseline"]["new"]),
"cleared": cleared, "dead": dead,
"tolerated": collections.Counter(r["baseline"]["class"]
for r in missing.values()
if r["baseline"]["known"])}
def report(rows, stats, rev, sha, corpus, ledger, path, only, context):
counts = collections.Counter(r["verdict"] for r in rows.values())
print("path_claim_check: %s at %s (%s)" % ("repo", rev, sha[:7]))
print("matcher: /?(?:[A-Za-z0-9_.@+-]+/)+[A-Za-z0-9_.@+-]+\\.(closed ext list), "
"at least one '/' required")
print(" graded only when the first component is a tracked top-level entry; "
"fenced blocks skipped")
print("corpus: %d markdown file(s)" % len(corpus))
print("paths: %d distinct / %d occurrence(s) (unit: distinct paths; "
"occurrences beside)" % (len(rows), stats["occurrences"]))
for v in VERDICTS:
print(" %-9s %4d distinct" % (v, counts[v]))
print("fenced skipped: %d line(s)" % stats["fenced_lines"])
if ledger is not None:
print("baseline %s: %d entries, %d matched, %d new, %d cleared, %d dead row(s)"
% (path, ledger["entries"], ledger["matched"], len(ledger["new"]),
len(ledger["cleared"]), len(ledger["dead"])))
for cls, n in sorted(ledger["tolerated"].items()):
print(" tolerated %-13s %d" % (cls, n))
for p in ledger["cleared"]:
print(" CLEARED %s -- ledger row, now in the tree; the row can go" % p)
for p in ledger["dead"]:
print(" DEAD_ROW %s -- ledger row, no longer in the census; the row can go" % p)
sel = {"missing": lambda r: r["verdict"] == "MISSING",
"exists": lambda r: r["verdict"] == "EXISTS",
"external": lambda r: r["verdict"] == "EXTERNAL",
"template": lambda r: r["verdict"] == "TEMPLATE",
"all": lambda r: True}[only]
show = sorted((r for r in rows.values() if sel(r)),
key=lambda r: (-r["mentions"], r["path"]))
print("--- %s: %d distinct path(s) (showing first 300) ---" % (only, len(show)))
for r in show[:300]:
tag = ""
if r["verdict"] == "MISSING" and r.get("baseline"):
b = r["baseline"]
tag = (" [%s: %s]" % (b["class"], b["reason"])) if b["known"] \
else " [NEW -- not in the baseline]"
print(" %3dx %-58s first %s%s" % (r["mentions"], r["path"], r["first"], tag))
if context:
print(" ...%s..." % r["context"])
def main():
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--repo", default=DEFAULT_REPO)
ap.add_argument("--rev", default="HEAD")
ap.add_argument("--corpus", nargs="*")
ap.add_argument("--json")
ap.add_argument("--only", choices=["missing", "exists", "external", "template",
"all"], default="missing")
ap.add_argument("--context", action="store_true")
ap.add_argument("--fail-on-missing", action="store_true")
ap.add_argument("--baseline")
ap.add_argument("--selftest", action="store_true")
a = ap.parse_args()
if a.selftest:
return selftest()
problem = precondition(a.repo, a.rev)
if problem:
print(problem, file=sys.stderr)
return 2
try:
store = Store(a.repo, a.rev)
sha = git(a.repo, "rev-parse", a.rev).stdout.decode().strip()
corpus = a.corpus if a.corpus is not None else \
sorted(p for p in store.tree if p.endswith((".md", ".markdown")))
if not corpus:
raise CannotGrade("cannot grade: no markdown corpus at %s in %s"
% (a.rev, a.repo))
ledger = load_baseline(a.baseline) if a.baseline else None
rows, stats = scan(a.repo, a.rev, corpus, store)
except CannotGrade as exc:
print(str(exc), file=sys.stderr)
return 2
led = apply_baseline(rows, ledger) if ledger is not None else None
if led is None:
# No ledger: every MISSING path is new, and the count is the finding.
led_view = None
else:
led_view = led
report(rows, stats, a.rev, sha, corpus, led_view, a.baseline, a.only, a.context)
missing = [r for r in rows.values() if r["verdict"] == "MISSING"]
new = len(missing) if led is None else len(led["new"])
print("\npath_claim_check: %d distinct paths, %d missing, %d new against the baseline"
% (len(rows), len(missing), new))
if a.json:
with open(a.json, "w") as fh:
json.dump({"repo": a.repo, "rev": a.rev, "sha": sha, "corpus": corpus,
"stats": dict(stats), "baseline": led,
"paths": sorted(rows.values(), key=lambda r: r["path"])},
fh, indent=1)
print("wrote %s" % a.json)
if a.fail_on_missing and new:
print("FAIL: %d missing path(s) the baseline does not name" % new)
return 1
return 0
# --------------------------------------------------------------- self-test
# The fixtures are graded by the same scan_text() the corpus is, so a cell that
# passes here is a statement about the shipped matcher, not about a copy of it.
TRACKED = {"docs/real.md", "tools/t.py", "notes/n.md", "WORK-QUEUE.md"}
TOPS = {"docs", "tools", "notes"}
def _verdicts(text):
rows, _ = scan_text(text, "fixture.md", TRACKED, TOPS)
return {p: r["verdict"] for p, r in rows.items()}
def _rows(text):
return scan_text(text, "fixture.md", TRACKED, TOPS)[0]
def selftest():
n = bad = 0
def cell(name, ok, detail=""):
nonlocal n, bad
n += 1
bad += 0 if ok else 1
print("%s %s%s" % ("PASS" if ok else "FAIL", name,
"" if ok else " <- " + detail))
cell("c1 a tracked path is EXISTS",
_verdicts("see docs/real.md for the table") == {"docs/real.md": "EXISTS"},
str(_verdicts("see docs/real.md for the table")))
cell("c2 an in-namespace path not in the tree is MISSING",
_verdicts("the record `docs/absent.md` was written") == {"docs/absent.md": "MISSING"},
str(_verdicts("the record `docs/absent.md` was written")))
cell("c3 a component this repo does not track is EXTERNAL, not MISSING",
_verdicts("kept at crucible/q51/helm-selftest-q51.py") ==
{"crucible/q51/helm-selftest-q51.py": "EXTERNAL"},
str(_verdicts("kept at crucible/q51/helm-selftest-q51.py")))
cell("c4 an absolute host path is EXTERNAL",
_verdicts("under /root/crucible/q39/gate.py today") ==
{"/root/crucible/q39/gate.py": "EXTERNAL"},
str(_verdicts("under /root/crucible/q39/gate.py today")))
cell("c5 a placeholder path is TEMPLATE, not MISSING",
_verdicts("one file per day: checkins/YYYY-MM-DD-volt.md") ==
{"checkins/YYYY-MM-DD-volt.md": "TEMPLATE"},
str(_verdicts("one file per day: checkins/YYYY-MM-DD-volt.md")))
cell("c6 a fenced path is quoted material, not a claim",
_verdicts("```\ndocs/absent.md\n```") == {},
str(_verdicts("```\ndocs/absent.md\n```")))
cell("c7 the fence closes: the same path in prose is graded again",
_verdicts("```\ndocs/absent.md\n```\nsee docs/absent.md") ==
{"docs/absent.md": "MISSING"},
str(_verdicts("```\ndocs/absent.md\n```\nsee docs/absent.md")))
cell("c8 a bare filename with no directory is not a path claim",
_verdicts("the page index.html and blocks/index.html") ==
{"blocks/index.html": "EXISTS" if "blocks/index.html" in TRACKED else "EXTERNAL"},
str(_verdicts("the page index.html and blocks/index.html")))
cell("c9 trailing punctuation and backticks are stripped",
list(_verdicts("`docs/absent.md`.").keys()) == ["docs/absent.md"],
str(_verdicts("`docs/absent.md`.")))
r = _rows("docs/absent.md once, then docs/absent.md again")
cell("c10 occurrences aggregate onto one row with its first cite",
len(r) == 1 and r["docs/absent.md"]["mentions"] == 2
and r["docs/absent.md"]["first"] == "fixture.md:1", str(r))
# --- the CLI contract, on a real fixture repo -------------------------
import tempfile
td = tempfile.mkdtemp(prefix="pathclaim-")
repo = os.path.join(td, "repo")
os.makedirs(os.path.join(repo, "docs"))
os.makedirs(os.path.join(repo, "tools"))
with open(os.path.join(repo, "docs", "real.md"), "w") as fh:
fh.write("real\n")
with open(os.path.join(repo, "tools", "t.py"), "w") as fh:
fh.write("# t\n")
with open(os.path.join(repo, "WORK-QUEUE.md"), "w") as fh:
fh.write("the record `docs/absent.md` and the real docs/real.md\n")
subprocess.run(["git", "init", "-q", repo], capture_output=True)
subprocess.run(["git", "-C", repo, "add", "-A"], capture_output=True)
subprocess.run(["git", "-C", repo, "-c", "user.email=t@t", "-c", "user.name=t",
"commit", "-qm", "fixture"], capture_output=True)
def run(*args):
p = subprocess.run([sys.executable, os.path.abspath(__file__), *args],
capture_output=True, text=True)
return p.returncode, p.stdout + p.stderr
rc, out = run("--repo", repo)
cell("c11 the census alone is rc 0 even with a missing path",
rc == 0 and "1 missing" in out, "rc=%d %s" % (rc, out[-200:]))
rc, out = run("--repo", repo, "--fail-on-missing")
cell("c12 --fail-on-missing is rc 1 on an unbaselined missing path",
rc == 1 and "FAIL: 1 missing path" in out, "rc=%d %s" % (rc, out[-200:]))
bl = os.path.join(td, "baseline.tsv")
with open(bl, "w") as fh:
fh.write("# fixture\n" + "docs/absent.md\tPLANTED\tfixture row\n")
rc, out = run("--repo", repo, "--fail-on-missing", "--baseline", bl)
cell("c13 a baselined missing path is tolerated, and still printed",
rc == 0 and "PLANTED" in out and "1 matched" in out,
"rc=%d %s" % (rc, out[-300:]))
with open(os.path.join(td, "cleared.tsv"), "w") as fh:
fh.write("docs/real.md\tRENAMED\tfixture: the row is now in the tree\n")
rc, out = run("--repo", repo, "--baseline", os.path.join(td, "cleared.tsv"))
cell("c14 a ledger row whose path now exists is CLEARED and named",
rc == 0 and "CLEARED" in out and "docs/real.md" in out,
"rc=%d %s" % (rc, out[-300:]))
rc, out = run("--repo", td)
cell("c15 a directory that is not a git work tree is rc 2",
rc == 2 and "not a git work tree" in out, "rc=%d %s" % (rc, out[-200:]))
rc, out = run("--repo", repo, "--rev", "nosuchrev")
cell("c16 a revision that does not resolve is rc 2",
rc == 2 and "cannot read" in out, "rc=%d %s" % (rc, out[-200:]))
empty = os.path.join(td, "empty")
os.makedirs(empty)
with open(os.path.join(empty, "a.txt"), "w") as fh:
fh.write("no markdown here\n")
subprocess.run(["git", "init", "-q", empty], capture_output=True)
subprocess.run(["git", "-C", empty, "add", "-A"], capture_output=True)
subprocess.run(["git", "-C", empty, "-c", "user.email=t@t", "-c", "user.name=t",
"commit", "-qm", "fixture"], capture_output=True)
rc, out = run("--repo", empty)
cell("c17 a corpus with no markdown is rc 2, never an empty clean census",
rc == 2 and "no markdown corpus" in out, "rc=%d %s" % (rc, out[-200:]))
print("selftest: %d cells, %d passed, %d failed" % (n, n - bad, bad))
return 0 if bad == 0 else 1
if __name__ == "__main__":
sys.exit(main())
+5
View File
@@ -165,6 +165,7 @@ READ_PROBES = (
("doc_census_check.py", ["--repo", "{nope}", "--no-run"]),
("gate_blame.py", ["--repo", "{nope}"]),
("landing_check.py", ["--repo", "{nope}"]),
("path_claim_check.py", ["--repo", "{nope}"]),
("queue_claim_check.py", ["--queue", "{nope}/q.md"]),
("queue_index.py", ["--queue", "{nope}/q.md", "--check", "{nope}/i.md"]),
("queue_append_audit.py", ["--repo", "{nope}"]),
@@ -229,6 +230,10 @@ MALFORMED_PROBES = (
2, "not a git work tree"),
("landing_check.py", "not-a-repo", ["--repo", "{notrepo}"],
2, "not a git work tree"),
# path_claim_check.py's input is a git work tree whose markdown names paths:
# a directory that is not one must be named, not graded as an empty census.
("path_claim_check.py", "not-a-repo", ["--repo", "{notrepo}"],
2, "not a git work tree"),
("queue_claim_check.py", "junk-queue", ["--queue", "{junkqueue}"],
0, "0 opening records"),
("queue_index.py", "junk-index", ["--queue", "{junkqueue}", "--check", "{junkindex}"],