51 lines
2.1 KiB
Python
51 lines
2.1 KiB
Python
|
|
"""Tests for programme.py parsing and validation."""
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from homemaker_layout import fitness, programme
|
||
|
|
|
||
|
|
# --------------------------------------------------------------------------- #
|
||
|
|
# homemaker-py-ju3 / DESIGN.md §39.2 — reserved generic type prefixes
|
||
|
|
# --------------------------------------------------------------------------- #
|
||
|
|
def test_validate_codes_accepts_non_reserved_prefixes():
|
||
|
|
programme.validate_codes(["b1", "k1", "t3", "la1", "me1", "n", "fr1", "ao"])
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("code", ["cr1", "of", "st1", "C", "O", "s2"])
|
||
|
|
def test_validate_codes_rejects_reserved_prefixes(code):
|
||
|
|
"""A colliding code must fail LOUDLY: silently reinterpreting it as a
|
||
|
|
generic type is the whole bug (§39.2)."""
|
||
|
|
with pytest.raises(ValueError, match="reserved generic type prefixes"):
|
||
|
|
programme.validate_codes([code])
|
||
|
|
|
||
|
|
|
||
|
|
def test_reserved_prefix_rejected_by_both_parse_paths():
|
||
|
|
"""programme._parse_spaces and fitness.Fitness._load_programme parse
|
||
|
|
conf["spaces"] independently — validating only one would leave the other
|
||
|
|
door open."""
|
||
|
|
conf = {"spaces": {"cr1": {"size": [80.0, 10.0]}}}
|
||
|
|
with pytest.raises(ValueError, match="reserved generic type prefixes"):
|
||
|
|
programme._parse_spaces(conf)
|
||
|
|
with pytest.raises(ValueError, match="reserved generic type prefixes"):
|
||
|
|
fitness.Fitness(conf=conf)
|
||
|
|
|
||
|
|
|
||
|
|
def test_semantic_but_unreserved_prefixes_are_allowed():
|
||
|
|
"""l/k/b/t carry adjacency semantics but never discard a requirement, so
|
||
|
|
programme codes may use them freely — only c/o/s are reserved."""
|
||
|
|
reqs = programme._parse_spaces({"spaces": {
|
||
|
|
"l1": {"size": [20.0, 4.0]}, "k1": {"size": [12.0, 3.0]},
|
||
|
|
"b1": {"size": [16.0, 4.0]}, "t1": {"size": [3.0, 1.0]},
|
||
|
|
}})
|
||
|
|
assert sorted(reqs) == ["b1", "k1", "l1", "t1"]
|
||
|
|
|
||
|
|
|
||
|
|
def test_corpus_programmes_are_namespace_clean():
|
||
|
|
"""Every checked-in example must load — a regression here means a corpus
|
||
|
|
programme reintroduced a colliding code."""
|
||
|
|
for d in sorted(Path("examples").iterdir()):
|
||
|
|
if (d / "patterns.config").is_file():
|
||
|
|
programme.load_programme_dir(str(d))
|