diff --git a/.gitattributes b/.gitattributes index 18454b2..91648a9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ -# The artwork under docs/art/ is generated, and a gate compares the committed -# bytes to a fresh render. Normalizing line endings per platform would make that -# comparison depend on who checked the repository out. +# The art under docs/art/ is generated. render_repo_art.py writes LF on every +# platform, so pin the checkout to LF as well and each file's byte count and +# SHA-256 stay the same whoever cloned the repository. docs/art/*.svg text eol=lf docs/art/*.json text eol=lf diff --git a/tests/test_repo_art.py b/tests/test_repo_art.py index 2b1e63f..d8e0360 100644 --- a/tests/test_repo_art.py +++ b/tests/test_repo_art.py @@ -25,6 +25,7 @@ "art.every_illustration_is_shown", "art.tagline_stays_inside_its_rule", "art.outcome_fits_its_box", + "art.the_gate_can_fail", ) DRAWINGS = ( diff --git a/tools/check_repo_art.py b/tools/check_repo_art.py index 3d70deb..1cd4559 100644 --- a/tools/check_repo_art.py +++ b/tools/check_repo_art.py @@ -130,8 +130,12 @@ def check_note_survives_the_wrapper(_unused: list[Path]) -> list[str]: """Card notes wrap to three lines and the wrapper drops the rest, so an edited sentence can lose its ending in the drawing while reading fine in the spec.""" + return _notes_the_wrapper_cuts(_loaded()) + + +def _notes_the_wrapper_cuts(specs: list[dict]) -> list[str]: bad = [] - for spec in _loaded(): + for spec in specs: for flow in spec.get("flows", []): for stage in flow["stages"]: drawn = " ".join(FLOW._wrap(stage["note"])) @@ -167,8 +171,12 @@ def check_every_illustration_is_shown(_unused: list[Path]) -> list[str]: def check_tagline_stays_inside_its_rule(_unused: list[Path]) -> list[str]: """The tagline is one unwrapped line under a rule that ends at x=700. Past that it runs on toward the aperture and nothing about the render fails.""" + return _taglines_that_overrun(_loaded()) + + +def _taglines_that_overrun(specs: list[dict]) -> list[str]: bad = [] - for spec in _loaded(): + for spec in specs: tagline = spec["header"]["tagline"] if len(tagline) > TAGLINE_BUDGET: bad.append(f"{len(tagline)} characters runs past the rule: {tagline!r}") @@ -185,8 +193,12 @@ def _outcome_budgets(count: int) -> tuple[int, int]: def check_outcome_fits_its_box(_unused: list[Path]) -> list[str]: """An outcome box is one unwrapped label over one unwrapped note, and neither is clipped, so an over-long note runs into the next box.""" + return _outcomes_that_overflow(_loaded()) + + +def _outcomes_that_overflow(specs: list[dict]) -> list[str]: bad = [] - for spec in _loaded(): + for spec in specs: for flow in spec.get("flows", []): label_budget, note_budget = _outcome_budgets(len(flow["outcomes"])) for item in flow["outcomes"]: @@ -198,6 +210,28 @@ def check_outcome_fits_its_box(_unused: list[Path]) -> list[str]: return bad +# A spec built to break all three geometry budgets at once. Every other check +# here reports clean, which says it ran and not that it works. +CONTROL = [{ + "header": {"tagline": "x" * (TAGLINE_BUDGET + 1)}, + "flows": [{ + "stages": [{"title": "CARD", "note": "word " * 60}], + "outcomes": [{"label": "OK", "note": "x" * 200}, + {"label": "y" * 200, "note": "short"}], + }], +}] + + +def check_the_gate_can_fail(_unused: list[Path]) -> list[str]: + """Feed the three geometry checks input they have to reject.""" + return [f"the gate missed {what}" for caught, what in ( + (len(_notes_the_wrapper_cuts(CONTROL)) == 1, "a truncated note"), + (len(_taglines_that_overrun(CONTROL)) == 1, "a tagline past its rule"), + (len(_outcomes_that_overflow(CONTROL)) == 2, + "an over-wide label and an over-long note"), + ) if not caught] + + CHECKS = [ ("spec.present", check_spec_present), ("art.matches_spec", check_artwork_matches_spec), @@ -211,6 +245,7 @@ def check_outcome_fits_its_box(_unused: list[Path]) -> list[str]: ("art.every_illustration_is_shown", check_every_illustration_is_shown), ("art.tagline_stays_inside_its_rule", check_tagline_stays_inside_its_rule), ("art.outcome_fits_its_box", check_outcome_fits_its_box), + ("art.the_gate_can_fail", check_the_gate_can_fail), ]