Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions tests/v2/test_differential.py
Original file line number Diff line number Diff line change
Expand Up @@ -961,8 +961,8 @@ def test_dormant_rules_reports_a_rule_whose_behavior_vanished() -> None:
report = compare.dormant_rules(
rules, {"fix(a)"}, [("John Smith", {"given"})])
assert report.awake == ()
assert [i for i, _ in report.undeclared] == ["fix(b)"]
assert "reverted" in report.undeclared[0][1]
assert [d.issue for d in report.undeclared] == ["fix(b)"]
assert report.undeclared[0].kind == "reverted"


def test_dormant_rules_names_the_rule_that_shadows_one() -> None:
Expand All @@ -976,8 +976,9 @@ def test_dormant_rules_names_the_rule_that_shadows_one() -> None:
"fields": ["given"]}]
report = compare.dormant_rules(
rules, {"fix(broad)"}, [("John Smith", {"given"})])
assert [i for i, _ in report.undeclared] == ["fix(narrow)"]
assert "shadowed by 'fix(broad)'" in report.undeclared[0][1]
assert [d.issue for d in report.undeclared] == ["fix(narrow)"]
assert report.undeclared[0].kind == "shadowed"
assert report.undeclared[0].detail == "fix(broad)"


def test_dormant_rules_distinguishes_an_excluded_shape() -> None:
Expand All @@ -988,8 +989,8 @@ def test_dormant_rules_distinguishes_an_excluded_shape() -> None:
never = [{"why": "protected", "name_regex": "Smith"}]
report = compare.dormant_rules(
rules, set(), [("John Smith", {"given"})], never)
assert [i for i, _ in report.undeclared] == ["fix(a)"]
assert "[[never]]" in report.undeclared[0][1]
assert [d.issue for d in report.undeclared] == ["fix(a)"]
assert report.undeclared[0].kind == "excluded"


def test_dormant_rules_is_silent_about_a_declared_rule() -> None:
Expand Down Expand Up @@ -1022,8 +1023,8 @@ def test_dormant_rules_names_the_shadower_that_does_the_shadowing() -> None:
rules, {"fix(a)", "fix(z)"},
[("Alpha One", {"given"}), ("Zeta One", {"given"}),
("Zeta Two", {"given"}), ("Zeta Three", {"given"})])
assert [i for i, _ in report.undeclared] == ["fix(idle)"]
assert "shadowed by 'fix(z)'" in report.undeclared[0][1]
assert [d.issue for d in report.undeclared] == ["fix(idle)"]
assert report.undeclared[0].detail == "fix(z)"


def test_dormant_rules_sorts_before_diagnosing() -> None:
Expand All @@ -1041,8 +1042,8 @@ def test_dormant_rules_sorts_before_diagnosing() -> None:
"fields": ["given"]}]
report = compare.dormant_rules(
rules, {"specific"}, [("John Smith", {"given"})])
assert [i for i, _ in report.undeclared] == ["broad"]
assert "shadowed by 'specific'" in report.undeclared[0][1]
assert [d.issue for d in report.undeclared] == ["broad"]
assert report.undeclared[0].detail == "specific"


def test_validate_rules_rejects_two_rules_sharing_an_issue() -> None:
Expand Down
57 changes: 41 additions & 16 deletions tools/differential/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import tomllib
from collections import Counter
from pathlib import Path
from typing import NamedTuple
from typing import Literal, NamedTuple

HERE = Path(__file__).resolve().parent
FIELDS = ("title", "first", "middle", "last", "suffix", "nickname",
Expand Down Expand Up @@ -639,11 +639,37 @@ def classify(name: str, diff_fields: set[str],
return None


#: How each `_Dormant.kind` reads in the report. Here rather than in
#: dormant_rules so the computation never has to know the wording, and
#: rephrasing one is a change to output alone.
_DORMANT_WHY = {
"reverted": ("matched no diffing name -- the behavior it describes "
"may have been reverted"),
"shadowed": "shadowed by {by}",
"excluded": ("every diffing name it matches is refused by a "
"[[never]] exclusion"),
}


class _Dormant(NamedTuple):
"""One rule that explained nothing, and why.

`kind` rather than a sentence because the three have three different
fixes, and a caller that wants to tell them apart should not have to
parse prose to do it. The wording lives in main(), which is the only
place that renders it -- so rephrasing a diagnosis stays a change to
output alone, and the tests that pin the DISTINCTION keep working.
"""
issue: str
kind: Literal["reverted", "shadowed", "excluded"]
#: the issue that claimed it, when `kind` is "shadowed"; else ""
detail: str


class _Dormancy(NamedTuple):
"""What a run found out about rules that explained nothing."""
#: (issue, diagnosis) for every rule that explained nothing and does
#: not declare `dormant`
undeclared: tuple[tuple[str, str], ...]
#: every rule that explained nothing and does not declare `dormant`
undeclared: tuple[_Dormant, ...]
#: issues declaring `dormant` that explained at least one diff
awake: tuple[str, ...]

Expand Down Expand Up @@ -690,18 +716,16 @@ def dormant_rules(rules: list[dict[str, object]], explained: set[str],
continue
matched = [(n, d) for n, d in diffing
if _entry_matches(rule, n, d)]
winners = Counter(
c for c in (classify(n, d, ordered, exclusions)
for n, d in matched) if c is not None)
if not matched:
why = ("matched no diffing name -- the behavior it describes "
"may have been reverted")
undeclared.append(_Dormant(issue, "reverted", ""))
elif winners:
undeclared.append(
_Dormant(issue, "shadowed", winners.most_common(1)[0][0]))
else:
winners = Counter(
c for c in (classify(n, d, ordered, exclusions)
for n, d in matched) if c is not None)
why = (f"shadowed by {winners.most_common(1)[0][0]!r}"
if winners else
"every diffing name it matches is refused by a "
"[[never]] exclusion")
undeclared.append((issue, why))
undeclared.append(_Dormant(issue, "excluded", ""))
return _Dormancy(tuple(undeclared), tuple(awake))


Expand Down Expand Up @@ -833,8 +857,9 @@ def main() -> int:
print(f" {n!r}")
print()
dormancy = dormant_rules(rules, set(by_issue), diffing, exclusions)
for issue, why in dormancy.undeclared:
print(f"EXPLAINED NOTHING {issue!r}\n {why}")
for dormant in dormancy.undeclared:
print(f"EXPLAINED NOTHING {dormant.issue!r}\n "
f"{_DORMANT_WHY[dormant.kind].format(by=repr(dormant.detail))}")
for issue in dormancy.awake:
print(f"NO LONGER DORMANT {issue!r}\n it explained a diff in "
f"this run, so its `dormant` reason is now false -- remove "
Expand Down