Make both failing tests assert their intent, not stale artefacts
The suite is green for the first time this session: 376 passed, 0 failed. test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both endpoints of the 17 result -- 15 fails before collapse, 12 after. Those were measured before 39.4, when harbor's effective programme was silently 32 instances because codes like cr1 were read as generic circulation; the same layout now scores 82. But the guarantee the test exists to protect, per its own docstring, is that in-search collapse reaches the SAME layout as finish-time collapse on fixed geometry -- and two independent constants never checked that. They can both drift and stay equal, or both hold and mask an inequality. Rewritten to compute both sides live and assert they agree, plus that collapse does not make the layout worse. Measured: 82 -> 58 in-search, and finish-time collapse independently reaches 58 at iters=3 and iters=6. The invariant holds; only the constants were stale. Restating the reference figure itself remains homemaker-py-ut5. test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the scorer writes beside a .dom -- so its docstring described files that by design never exist in the repo, and it passed only on a machine that had already run the scorer. Split into: a test that GENERATES fails by scoring three corpus layouts picked for breadth (requiring >= 8 distinct kinds so it cannot silently narrow); a test that an unclassifiable string actually raises; and an opportunistic .fails sweep that never requires them. Verified by moving every .fails out of the tree and re-running. Closes homemaker-py-1ue. Lint at parity (46). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
This commit is contained in:
parent
e5eb397b52
commit
f5286dde3d
3 changed files with 95 additions and 25 deletions
File diff suppressed because one or more lines are too long
|
|
@ -95,21 +95,41 @@ def test_evaluate_full_does_not_call_collapse_global_when_off():
|
|||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
||||
def test_collapse_insearch_reproduces_94g_finish_time_result():
|
||||
# DESIGN.md §17: the finish-time collapse takes this layout 15 -> 12 fails.
|
||||
# collapse_insearch runs the SAME collapse_global earlier in the SAME
|
||||
# pipeline (before Phase-1 checks instead of after the whole search), so it
|
||||
# must reach the identical fail count on this fixed-geometry layout.
|
||||
def test_collapse_insearch_matches_finish_time_collapse():
|
||||
"""in-search collapse must reach the SAME layout as finish-time collapse.
|
||||
|
||||
That equality is the actual guarantee: `collapse_insearch` runs the same
|
||||
`collapse_global` earlier in the same pipeline (before the Phase-1 checks
|
||||
rather than after the whole search), so on a FIXED geometry the two must
|
||||
agree. Both sides are computed here rather than hard-coded.
|
||||
|
||||
This test previously asserted the §17 constants directly -- `15` fails
|
||||
before collapse and `12` after. Those were measured before §39.4, when
|
||||
harbor's effective programme was silently 32 instances because codes like
|
||||
`cr1` were being read as generic circulation; the same layout now scores 82
|
||||
-> 58. Pinning the endpoints made a live invariant fail whenever the
|
||||
programme or the objective legitimately changed, while not actually
|
||||
checking the invariant at all (two independent constants can both drift and
|
||||
still be equal, or both hold and mask an inequality). See
|
||||
`homemaker-py-ut5` for restating the reference figure itself.
|
||||
"""
|
||||
conf, cost = load_config(HARBOR)
|
||||
conf_ci, _ = load_config(HARBOR, overrides={"collapse_insearch": True})
|
||||
fit, fit_ci = Fitness(conf, cost), Fitness(conf_ci, cost)
|
||||
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
|
||||
|
||||
_, f_base = fit.score_with_fails(copy.deepcopy(root))
|
||||
_, f_ci = fit_ci.score_with_fails(copy.deepcopy(root))
|
||||
_, f_base = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
|
||||
_, f_ci = Fitness(conf_ci, cost).score_with_fails(copy.deepcopy(root))
|
||||
|
||||
assert len(f_base) == 15
|
||||
assert len(f_ci) == 12
|
||||
# the same collapse, applied once at finish time, scored canonically
|
||||
finished = copy.deepcopy(root)
|
||||
Fitness(conf, cost).collapse_global(
|
||||
finished, adjacency=True, objective="threshold",
|
||||
preserve_public_access=True, iters=3)
|
||||
_, f_finish = Fitness(conf, cost).score_with_fails(finished)
|
||||
|
||||
assert len(f_ci) == len(f_finish), (
|
||||
"in-search collapse diverged from finish-time collapse on fixed geometry")
|
||||
assert len(f_ci) < len(f_base), "collapse must not make the layout worse"
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"""Unit tests for fitness.py quality terms and helpers (oracle-free)."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from _helpers import with_usage
|
||||
|
|
@ -12,6 +14,7 @@ from homemaker_layout.fitness import (
|
|||
Fitness,
|
||||
_leaf_grade,
|
||||
classify_fail_tier,
|
||||
load_config,
|
||||
gaussian,
|
||||
tier_counts,
|
||||
)
|
||||
|
|
@ -449,26 +452,73 @@ def test_tier_counts_empty():
|
|||
assert tier_counts(()) == (0, 0)
|
||||
|
||||
|
||||
def test_classify_fail_tier_covers_full_corpus():
|
||||
"""Regression guard: every fail string ever emitted into a checked-in
|
||||
native (non-YAML) .fails file must still classify without error."""
|
||||
import glob
|
||||
from pathlib import Path
|
||||
# Layouts chosen for BREADTH of failure kinds, not for being good designs --
|
||||
# between them these emit size/width/proportion/crinkliness/access/adjacency,
|
||||
# missing-space cascades, connectivity and volume fails.
|
||||
_CORPUS_LAYOUTS = [
|
||||
("harbor-house", "evolved-3M-nols-3.dom"),
|
||||
("harbor-house", "generated.dom"),
|
||||
("maple-court", "generated.dom"),
|
||||
]
|
||||
|
||||
|
||||
def test_classify_fail_tier_covers_every_fail_the_evaluator_emits():
|
||||
"""Every fail string the evaluator can produce must classify into a tier.
|
||||
|
||||
Fails are GENERATED here by scoring corpus layouts. The previous version
|
||||
globbed `examples/**/*.fails` and asserted it had checked something -- but
|
||||
those are generated artefacts that `homemaker-fitness` writes beside a
|
||||
`.dom`, absent from a clean checkout. So it passed only on a machine that
|
||||
had already run the scorer, and in a fresh clone failed with `assert 0 > 0`:
|
||||
it was asserting on the state of the developer's working tree, not on the
|
||||
code (`homemaker-py-1ue`).
|
||||
"""
|
||||
import copy
|
||||
|
||||
from homemaker_layout import dom as dom_mod
|
||||
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
checked = 0
|
||||
for path in glob.glob(str(repo_root / "examples" / "**" / "*.fails"), recursive=True):
|
||||
checked = kinds = 0
|
||||
seen: set[str] = set()
|
||||
for prog, name in _CORPUS_LAYOUTS:
|
||||
path = repo_root / "examples" / prog / name
|
||||
if not path.is_file():
|
||||
continue
|
||||
conf, cost = load_config(repo_root / "examples" / prog)
|
||||
_, fails = Fitness(conf, cost).score_with_fails(
|
||||
copy.deepcopy(dom_mod.load(str(path))))
|
||||
for fail in fails:
|
||||
classify_fail_tier(fail) # raises on an unclassified string
|
||||
checked += 1
|
||||
seen.add(fail.split()[-1])
|
||||
kinds = len(seen)
|
||||
assert checked > 0, "no corpus layout could be scored -- fixtures missing?"
|
||||
assert kinds >= 8, f"only {kinds} distinct fail kinds exercised; too narrow"
|
||||
|
||||
|
||||
def test_classify_fail_tier_rejects_an_unknown_fail_string():
|
||||
"""The guard above is only worth anything if an unclassifiable string
|
||||
actually raises."""
|
||||
with pytest.raises(ValueError, match="unclassified fail string"):
|
||||
classify_fail_tier("0/lr something nobody has ever emitted")
|
||||
|
||||
|
||||
def test_classify_fail_tier_checks_any_native_fails_artefacts_present():
|
||||
"""If a working tree happens to carry .fails artefacts, check them too --
|
||||
but never require them to exist."""
|
||||
import glob
|
||||
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
for path in glob.glob(str(repo_root / "examples" / "**" / "*.fails"),
|
||||
recursive=True):
|
||||
with open(path) as f:
|
||||
first = f.readline()
|
||||
if first.startswith("---"):
|
||||
continue # legacy Perl-oracle YAML .fails, not this evaluator's output
|
||||
continue # legacy Perl-oracle YAML, not this evaluator
|
||||
lines = [first.rstrip("\n")] + [ln.rstrip("\n") for ln in f]
|
||||
for line in lines:
|
||||
if not line:
|
||||
continue
|
||||
classify_fail_tier(line) # raises on failure
|
||||
checked += 1
|
||||
assert checked > 0
|
||||
if line:
|
||||
classify_fail_tier(line)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue