Skip to content

Commit 04d678e

Browse files
committed
Name the ledger a malformed rule came from
1 parent c8a203d commit 04d678e

2 files changed

Lines changed: 25 additions & 9 deletions

File tree

tests/v2/test_differential.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,13 @@ def test_every_ledger_rule_names_roles_canonically() -> None:
210210
])
211211
def test_latin_only_partition(name: str, latin: bool) -> None:
212212
assert compare._is_latin_only(name) is latin
213+
214+
215+
def test_malformed_rule_error_names_the_ledger_it_came_from() -> None:
216+
"""There is one ledger per baseline now, so a hardcoded filename
217+
sends the reader to edit a rule that is not the broken one."""
218+
bad = [{"issue": "x"}] # neither name_regex nor fields
219+
with pytest.raises(SystemExit, match="expected_since_2.0.0.toml"):
220+
compare.validate_rules(bad, "expected_since_2.0.0.toml")
221+
with pytest.raises(SystemExit, match="expected_since_1.4.0.toml"):
222+
compare.validate_rules([{}], "expected_since_1.4.0.toml")

tools/differential/compare.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -253,21 +253,26 @@ def _is_latin_only(name: str) -> bool:
253253
return all(ord(ch) < 0x250 for ch in name)
254254

255255

256-
def validate_rules(rules: list[dict[str, object]]) -> None:
256+
def validate_rules(rules: list[dict[str, object]], ledger: str) -> None:
257257
"""Reject malformed allowlist rules LOUDLY at startup. A rule with
258258
neither name_regex nor fields would match every diff and shadow
259259
every later rule -- the harness would report false confidence,
260-
the exact failure it exists to prevent."""
260+
the exact failure it exists to prevent.
261+
262+
`ledger` is named rather than hardcoded because there is one per
263+
baseline now: a message naming the wrong file sends the reader to
264+
edit a rule that is not the broken one.
265+
"""
261266
for i, rule in enumerate(rules):
262267
issue = rule.get("issue")
263268
if not isinstance(issue, str) or not issue:
264269
raise SystemExit(
265-
f"expected_since_1.4.0.toml rule #{i + 1} has no string "
270+
f"{ledger} rule #{i + 1} has no string "
266271
f"'issue': {rule!r}")
267272
if not isinstance(rule.get("name_regex"), str) \
268273
and not isinstance(rule.get("fields"), list):
269274
raise SystemExit(
270-
f"expected_since_1.4.0.toml rule #{i + 1} ({issue!r}) has "
275+
f"{ledger} rule #{i + 1} ({issue!r}) has "
271276
f"neither 'name_regex' nor 'fields' -- it would match "
272277
f"every diff and shadow every later rule")
273278

@@ -287,9 +292,10 @@ def classify(name: str, diff_fields: set[str],
287292

288293
def main() -> int:
289294
ap = argparse.ArgumentParser()
290-
# Both corpora by default: they have different blind spots (see
295+
# Every corpus by default: they have different blind spots (see
291296
# build_issues_corpus.py), and one that has to be asked for by name
292-
# is one that stops being run.
297+
# is one that stops being run. Deliberately a glob rather than a
298+
# list, so adding a corpus file is enough to put it in the gate.
293299
ap.add_argument("--corpus", action="append", metavar="PATH",
294300
help="corpus file; repeatable. Defaults to every "
295301
"corpus*.jsonl beside this script.")
@@ -303,9 +309,9 @@ def main() -> int:
303309
surfaces = _surfaces_for(baseline)
304310
paths = ([Path(p) for p in args.corpus] if args.corpus
305311
else sorted(HERE.glob("corpus*.jsonl")))
306-
rules = tomllib.loads(
307-
_allowlist_for(baseline).read_text()).get("change", [])
308-
validate_rules(rules)
312+
ledger = _allowlist_for(baseline)
313+
rules = tomllib.loads(ledger.read_text()).get("change", [])
314+
validate_rules(rules, ledger.name)
309315
rules = _sorted_rules(rules)
310316
# A glob that matches nothing must not read as "everything passed".
311317
# Comparing zero names would print 0 unexplained and exit 0 -- the

0 commit comments

Comments
 (0)