#!/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())