Files
settled-reach/tooling/test_canvas_version_check.py
jpmschweitzerandClaude Opus 5 a3cbc478a0 feat(config): T-1281 — canvas-version, and typer's other rich path
All five gates now live in the check domain. canvas-version produces
byte-identical output to the original on the live tree.

It is the first real consumer of core/process.run. The git calls pass
check=False deliberately: a git failure here is not an error to report but a
signal that there is nothing to compare, since a fresh clone with no remote is
a legitimate state rather than a broken one. The argv-list and missing-binary
guards still apply.

Its two skips are kept distinct from its pass. NO_BASE and DIFF_FAILED exit 0,
as does CLEAN — but only CLEAN means the gate actually looked at something.
Collapsing them would hide a gate that had silently stopped running, which for
this check in particular is the exact failure it exists to prevent.

Found a second rich path while a NameError was rendering as a full-width
box-drawn traceback: typer's pretty-exception handler is a different mechanism
from rich_markup_mode, and setting one does nothing about the other. Same log
pollution T-1259 thought it had closed, arriving through another door and
landing in the worst place — a hook log at the moment something has already
gone wrong. pretty_exceptions_enable=False now on the root and on every domain
built by cli.domain().

test_canvas_version_check.py moves with the code it guards. It had been loading
the extensionless script through a SourceFileLoader and reaching canvas_sources
by sys.path insert, both only because tooling/ was not importable. Second
instance of that debt evaporating on contact. What it asserts is unchanged,
which is the point: diff_has_version_bump was kept pure in the port so its six
properties still hold without constructing git history.

Also restores an import the check router dropped in T-1267 when it moved to
cli.domain() — caught by running the command rather than by reading it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 18:22:25 +02:00

162 lines
5.6 KiB
Python

#!/usr/bin/env python3
"""Units for the canvas-generation/version pairing gate (T-1242).
The gate's whole value is that it fires on the cases that shipped broken and
stays quiet otherwise. Two properties carry that, and neither is observable from
"the check passed on this branch":
1. Editing project.yaml WITHOUT moving `version:` is not a bump. The file
carries a comment block that quotes old version numbers (0.4.2 … 0.4.6), so
a naive "did project.yaml change" or "does the diff mention a version"
test would count commentary edits as a bump and wave through exactly the
regressions this exists to catch.
2. The registry is non-empty and covers the files the five known regressions
actually touched. An empty or narrowed registry passes every push silently.
Run: python3 tooling/test_canvas_version_check.py
"""
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
# Re-pointed at the ported service (T-1281). This test previously loaded the
# extensionless tooling/check-canvas-version through a SourceFileLoader and
# reached canvas_sources via a sys.path insert — both because tooling/ was not
# an importable package. Neither is needed now, and the test moves with the code
# it guards rather than being left aimed at a script due for retirement.
sys.path.insert(0, str(REPO_ROOT))
from tooling.canvas_sources import relative_paths # noqa: E402
from tooling.domains.check import service as check # noqa: E402
FAILURES: list[str] = []
def expect(condition: bool, label: str) -> None:
if condition:
print(f" ok {label}")
else:
print(f" FAIL {label}")
FAILURES.append(label)
def test_real_bump_counts() -> None:
diff = (
"--- a/project.yaml\n"
"+++ b/project.yaml\n"
"@@ -22 +22 @@\n"
"-version: 0.4.5\n"
"+version: 0.4.6\n"
)
expect(check.diff_has_version_bump(diff), "a real -version/+version pair is a bump")
def test_comment_edit_is_not_a_bump() -> None:
# The exact shape that must NOT count: adding a line to the comment block
# that documents what earlier versions carried. It mentions version numbers
# and sits directly above the field.
diff = (
"--- a/project.yaml\n"
"+++ b/project.yaml\n"
"@@ -20,0 +21,2 @@\n"
"+# 0.4.5-tagged canvases carry pre-T-1237 river courses: one course per\n"
"+# D8 hop rather than one per river. 0.4.6 forces them to miss.\n"
)
expect(
not check.diff_has_version_bump(diff),
"a comment-block edit quoting version numbers is NOT a bump",
)
def test_other_field_edit_is_not_a_bump() -> None:
diff = (
"--- a/project.yaml\n"
"+++ b/project.yaml\n"
"@@ -23 +23 @@\n"
"-repository: settled-reach\n"
"+repository: settled-reach-renamed\n"
)
expect(
not check.diff_has_version_bump(diff),
"editing another field is NOT a bump",
)
def test_diff_header_is_not_a_bump() -> None:
# `+++ b/project.yaml` starts with '+' and must not be mistaken for content.
diff = "--- a/project.yaml\n+++ b/project.yaml\n@@ -1 +1 @@\n-name: x\n+name: y\n"
expect(
not check.diff_has_version_bump(diff),
"the +++ diff header is NOT a bump",
)
def test_deletion_alone_is_not_a_bump() -> None:
diff = "--- a/project.yaml\n+++ b/project.yaml\n@@ -22 +21,0 @@\n-version: 0.4.6\n"
expect(
not check.diff_has_version_bump(diff),
"removing the version field is NOT a bump",
)
def test_empty_diff_is_not_a_bump() -> None:
expect(not check.diff_has_version_bump(""), "an empty diff is NOT a bump")
def test_registry_is_populated() -> None:
paths = relative_paths()
expect(len(paths) > 10, f"registry is populated ({len(paths)} paths)")
def test_registry_covers_the_known_regressions() -> None:
"""The files the five documented stale-cache regressions actually touched.
If a future edit narrows the registry past any of these, the gate stops
catching the exact class of bug it was built for — silently. This is the
test that makes such a narrowing loud.
"""
paths = set(relative_paths())
required = [
# T-1237 / T-1239: one course per river (0.4.6)
"server/src/atlas/step_canvas.rs",
"server/src/atlas/river_course.rs",
"client/ui/implant/apps/atlas/step_canvas/step_canvas_annotation_layer.gd",
# D-255 extent inversion (0.4.4) + Global sentinel (0.4.5)
"client/ui/implant/apps/atlas/step_canvas/step_canvas_transport.gd",
# coast_warp_px at orbital sampling (0.4.3)
"server/src/atlas/coast_invention.rs",
# lake_margin_q semantics (0.4.2)
"server/src/atlas/hydrology_equilibrium.rs",
# the layer every rung samples
"server/src/atlas/layer1.rs",
"server/src/atlas/district_profile.rs",
# the cache that the version tag actually governs
"client/ui/implant/apps/atlas/step_canvas/step_canvas_disk_cache.gd",
]
for path in required:
expect(path in paths, f"registry covers {path}")
def main() -> int:
print("test_canvas_version_check:")
test_real_bump_counts()
test_comment_edit_is_not_a_bump()
test_other_field_edit_is_not_a_bump()
test_diff_header_is_not_a_bump()
test_deletion_alone_is_not_a_bump()
test_empty_diff_is_not_a_bump()
test_registry_is_populated()
test_registry_covers_the_known_regressions()
if FAILURES:
print(f"\nFAILED ({len(FAILURES)}): " + "; ".join(FAILURES), file=sys.stderr)
return 1
print("test_canvas_version_check: PASS")
return 0
if __name__ == "__main__":
sys.exit(main())