This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""hub_gofmt_gate.py — every Go file in the hub module is gofmt-clean (R-1160; the controller's R-454 gate, same shape).
|
||||
|
||||
MEASURED 2026-10-19: 22 hub files were not gofmt-clean and nothing noticed (fixed by one formatting-only commit,
|
||||
d5e3c8b0). The controller's history below is why a gate and not a one-off clean-up.
|
||||
|
||||
MEASURED 2026-09-02: five internal/web test files had been gofmt-unclean for an unknown length of
|
||||
time and nothing noticed — `go vet` does not check formatting and no gate did. By 2026-10-05 the five
|
||||
were clean and TWELVE others were not: the count only grows while nothing looks, and every `gofmt -l`
|
||||
someone runs by hand is then noise that hides a real one.
|
||||
|
||||
Run from the repo root: python3 scripts/hub_gofmt_gate.py [<module dir>] (default: hub/)
|
||||
Exit 0 clean · 1 convicted (lists the files) · 2 inconclusive (gofmt not found — never a pass).
|
||||
|
||||
gofmt is the Go toolchain's own formatter (not a shell utility), found on PATH or under
|
||||
`go env GOROOT`/bin. Decoy: scripts/test_gate_decoys.py ("hub-gofmt"), on a scratch module dir.
|
||||
"""
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "hub")
|
||||
ROOTS = ["cmd", "internal"]
|
||||
|
||||
|
||||
def find_gofmt():
|
||||
p = shutil.which("gofmt")
|
||||
if p:
|
||||
return p
|
||||
go = shutil.which("go")
|
||||
if go:
|
||||
try:
|
||||
root = subprocess.check_output([go, "env", "GOROOT"], text=True).strip()
|
||||
cand = os.path.join(root, "bin", "gofmt")
|
||||
if os.path.isfile(cand):
|
||||
return cand
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def in_ci():
|
||||
"""True on the Gitea Actions runner (act sets GITHUB_ACTIONS, Gitea sets GITEA_ACTIONS)."""
|
||||
return os.environ.get("GITEA_ACTIONS") == "true" or os.environ.get("GITHUB_ACTIONS") == "true"
|
||||
|
||||
|
||||
def main():
|
||||
global ROOT
|
||||
if len(sys.argv) > 1:
|
||||
ROOT = os.path.abspath(sys.argv[1])
|
||||
gofmt = find_gofmt()
|
||||
if not gofmt:
|
||||
# The CI runner may have no Go toolchain (felhom.eu CLAUDE.md, R-887 note). There the gate
|
||||
# says so OUT LOUD and passes: the pre-push hook on the developer's machine, where Go always exists, is where
|
||||
# formatting is enforced. Anywhere else a missing gofmt stays INCONCLUSIVE — never a silent pass.
|
||||
# Pinned by the "hub-gofmt/ci-without-go" and "hub-gofmt/dev-without-go" decoys in test_gate_decoys.py.
|
||||
if in_ci():
|
||||
print("gofmt gate NOT CHECKED in CI: no Go toolchain on the runner — the pre-push hook (with Go) enforces it")
|
||||
return 0
|
||||
print("gofmt gate INCONCLUSIVE: gofmt not found on PATH or under `go env GOROOT`/bin")
|
||||
return 2
|
||||
roots = [r for r in ROOTS if os.path.isdir(os.path.join(ROOT, r))]
|
||||
if not roots:
|
||||
print("gofmt gate INCONCLUSIVE: none of %s exists under %s" % (ROOTS, ROOT))
|
||||
return 2
|
||||
p = subprocess.run([gofmt, "-l"] + roots, cwd=ROOT, capture_output=True, text=True)
|
||||
if p.returncode != 0:
|
||||
print("gofmt gate INCONCLUSIVE: gofmt exited %d: %s" % (p.returncode, p.stderr.strip()[:400]))
|
||||
return 2
|
||||
bad = [l for l in p.stdout.splitlines() if l.strip()]
|
||||
if bad:
|
||||
for f in bad:
|
||||
print(" not gofmt-clean: %s" % f)
|
||||
print("GOFMT GATE FAILED: %d file(s) — run `gofmt -w <file>` (formatting only, no behaviour change)" % len(bad))
|
||||
return 1
|
||||
print("gofmt gate OK — every Go file under %s is gofmt-clean" % ", ".join(roots))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -32,6 +32,7 @@ Gates, in order (all must pass; **non-zero exit on any failure**):
|
||||
decoy — FULL RUNS ONLY, "not checked" (exit 2) without docker (R-502, decision 147)
|
||||
16. decoy-coverage every registered gate in all four repos has a decoy, or a named exemption (R-421)
|
||||
17. poster-facts WARNS (never fails) when felhom-system-poster.facts.md is newer than the poster
|
||||
17b. hub-gofmt every hub Go file is gofmt-clean (R-1160; the controller's R-454 shape)
|
||||
|
||||
**THE `GATES` TABLE BELOW IS THE LIST; THIS IS A POINTER TO IT.** It drifted once already —
|
||||
it read eleven while thirteen were registered, from 2026-08-24 until 2026-09-01, so
|
||||
@@ -198,6 +199,9 @@ GATES = [
|
||||
# until a human with another tool was free -- and `--no-verify` is forbidden here, so the only
|
||||
# way out would be deleting the gate. Fast: two `git log -1` calls.
|
||||
("poster-facts", os.path.join(SCRIPTS, "poster_facts_gate.py"), [ROOT], True, False),
|
||||
# R-1160 (2026-10-19) — every hub Go file is gofmt-clean; the controller's gofmt_gate.py shape. Fast: one gofmt -l.
|
||||
# The CI runner has no Go: there it says NOT CHECKED out loud and passes; the pre-push hook (with Go) enforces it.
|
||||
("hub-gofmt", os.path.join(SCRIPTS, "hub_gofmt_gate.py"), [], True, False),
|
||||
]
|
||||
|
||||
VERDICT = {0: "OK", 1: "FAILED", 2: "INCONCLUSIVE"}
|
||||
|
||||
@@ -110,6 +110,9 @@ COVERS = {
|
||||
"one-register": ("R-426: an open roadmap row whose state NAMES another row's CLOSED (a live hole until "
|
||||
"2026-10-06), its id only in register prose, a suffix id; genuine: a row with a counterpart, "
|
||||
"and an `idea` row (R-424's accepted hole, asserted BY DESIGN)"),
|
||||
"hub-gofmt": ("R-1160: an unformatted .go file in a SCRATCH module dir is convicted by name; its "
|
||||
"formatted twin passes; with no gofmt reachable, CI passes OUT LOUD and a dev machine is "
|
||||
"INCONCLUSIVE, never a silent pass"),
|
||||
"hub-copy": ("R-558: an English retrieval promise in the NEW bundle (the sentences moved "
|
||||
"out of templates.go, so the surface list had to move with them), the same "
|
||||
"in Hungarian, and an INNOCENT control using the identical verbs without the "
|
||||
@@ -1079,6 +1082,45 @@ else:
|
||||
print("ok poster-facts/output-is-ascii")
|
||||
|
||||
|
||||
# --- hub-gofmt (R-1160): the gate on a scratch module dir, never the real hub tree. ----------------------------
|
||||
import tempfile as _tf
|
||||
_gd = _tf.mkdtemp(prefix="hub-gofmt-decoy-")
|
||||
try:
|
||||
os.makedirs(os.path.join(_gd, "internal", "x"))
|
||||
_gp = os.path.join(_gd, "internal", "x", "zz_gofmt_decoy.go")
|
||||
io.open(_gp, "w", encoding="utf-8").write(u"package x\n\nfunc F( ) int {return 1}\n")
|
||||
if shutil.which("gofmt") or shutil.which("go"):
|
||||
ran += 1
|
||||
_rc, _out = gate("hub_gofmt_gate.py", [_gd])
|
||||
if _rc != 1 or "zz_gofmt_decoy.go" not in _out:
|
||||
fails.append("hub-gofmt: an unformatted planted file was not convicted (rc=%d)\n%s" % (_rc, _out[-400:]))
|
||||
else:
|
||||
print("ok hub-gofmt/unformatted-convicted")
|
||||
ran += 1
|
||||
io.open(_gp, "w", encoding="utf-8").write(u"package x\n\nfunc F() int { return 1 }\n")
|
||||
_rc, _out = gate("hub_gofmt_gate.py", [_gd])
|
||||
if _rc != 0:
|
||||
fails.append("hub-gofmt: the formatted twin did not pass (rc=%d)\n%s" % (_rc, _out[-400:]))
|
||||
else:
|
||||
print("ok hub-gofmt/formatted-passes")
|
||||
else:
|
||||
print(" -- hub-gofmt not runnable here (no Go toolchain) — the two cases below pin that mode")
|
||||
for _ci, _want_rc, _want_txt, _name in ((True, 0, "NOT CHECKED in CI", "hub-gofmt/ci-without-go"),
|
||||
(False, 2, "INCONCLUSIVE", "hub-gofmt/dev-without-go")):
|
||||
ran += 1
|
||||
_env = {"PATH": os.path.join(_gd, "empty-path"), "HOME": _gd}
|
||||
if _ci:
|
||||
_env["GITEA_ACTIONS"] = "true"
|
||||
_p = subprocess.run([sys.executable, os.path.join("scripts", "hub_gofmt_gate.py"), _gd], cwd=ROOT, env=_env,
|
||||
capture_output=True, text=True)
|
||||
if _p.returncode != _want_rc or _want_txt not in _p.stdout:
|
||||
fails.append("%s: rc=%d, want %d with %r\n%s" % (_name, _p.returncode, _want_rc, _want_txt, _p.stdout[-300:]))
|
||||
else:
|
||||
print("ok %s" % _name)
|
||||
finally:
|
||||
shutil.rmtree(_gd, ignore_errors=True)
|
||||
|
||||
|
||||
print()
|
||||
if fails:
|
||||
for f in fails:
|
||||
|
||||
Reference in New Issue
Block a user