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>
162 lines
5.6 KiB
Python
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())
|