diff --git a/tests/guards/test_debt_ratchet.py b/tests/guards/test_debt_ratchet.py index c0e570438f..9c725172c9 100644 --- a/tests/guards/test_debt_ratchet.py +++ b/tests/guards/test_debt_ratchet.py @@ -148,8 +148,8 @@ def test_a_new_store_lock_call_fails_on_its_own_file_with_its_line(repo): " with store.locked():\n pass\n"}) status, report = repo.compare(base, head, StoreLockCalls) assert status == 1 - grown = StoreLockCalls.key(f"{repo.root}/other.py") - assert report["delta"] == {StoreLockCalls.key(f"{repo.root}/turns.py"): -1, grown: 1} + grown = StoreLockCalls.at(f"{repo.root}/other.py").text + assert report["delta"] == {StoreLockCalls.at(f"{repo.root}/turns.py").text: -1, grown: 1} assert report["found"] == {grown: [{"path": f"{repo.root}/other.py", "line": line} for line in (2, 4)]} @@ -173,6 +173,7 @@ def __init__(self, path: Path, root: str): self.path = path self.root = root self.git("init", "-q") + self.git("remote", "add", "origin", str(SOURCE)) def git(self, *args: str) -> str: return subprocess.check_output( @@ -377,7 +378,6 @@ def test_a_done_phase_fails_the_change_that_breaks_it(tmp_path: Path) -> None: objects = subprocess.check_output(["git", "-C", str(SOURCE), "rev-parse", "--path-format=absolute", "--git-common-dir"], text=True).strip() (tmp_path / ".git/objects/info/alternates").write_text(f"{objects}/objects\n") - repo.git("remote", "add", "origin", str(SOURCE)) owned = "class Owned:\n pass\n" base = repo.commit({"agent_comms/owned.py": owned}) head = repo.commit({"agent_comms/owned.py": owned + "\n\ndef owns(value):\n" diff --git a/tools/debt_ratchet.py b/tools/debt_ratchet.py index 5011755efc..79af5b31f7 100644 --- a/tools/debt_ratchet.py +++ b/tools/debt_ratchet.py @@ -163,8 +163,8 @@ def blocks(cls, delta: Counts) -> bool: @classmethod def grew(cls, key: MeasureCount, grown: int) -> str: - """The line reporting one count's growth.""" - return f"grew {key.text} +{grown}" + """The line reporting one count's growth; the count words it.""" + return key.grew(grown) @classmethod def run_by(cls) -> type[Measure]: @@ -226,6 +226,10 @@ def warning(self, text: str) -> str: """A check annotation for this count; a total annotates no file.""" return f"::warning::{text}" + def grew(self, grown: int) -> str: + """The line saying this count grew.""" + return f"grew {self.text} +{grown}" + @dataclass(frozen=True) class FileCount(MeasureCount): @@ -259,6 +263,21 @@ def text(self) -> str: return f"{super().text}::{self.name}" +@dataclass(frozen=True) +class PhaseSiteCount(MeasureCount): + """One site where a done plan phase's evidence fails.""" + + phase: type[PlanPhase] + site: str + + @property + def text(self) -> str: + return f"{super().text}:{self.phase.__name__}::{self.site}" + + def grew(self, grown: int) -> str: + return f"done plan phase {self.phase.__name__} no longer holds: {self.site}" + + # One revision's counts, each under the record of what it counts. type Counts = dict[MeasureCount, int] @@ -2604,32 +2623,27 @@ class DonePhaseBroken(Measure): @classmethod def parts(cls, repo: Path, base: str, head: str, changed: set[str], root: str) -> tuple[Callable[[], object], ...]: - declared = Path(sys.modules[PlanPhase.__module__].__file__ or "").resolve() + declared = Path(inspect.getfile(PlanPhase)).resolve() if root != f"{declared.relative_to(REPOSITORY).parts[0]}/": return () return tuple(partial(cls.failing, repo, revision(repo, ref)) for ref in (base, head)) @classmethod def combine(cls, results: list, repo: Path, base: str, head: str, changed: set[str], - root: str) -> tuple[dict[str, int], dict[str, int]]: + root: str) -> tuple[Counts, Counts]: """A failing pull request pays for reading its sites; a passing one never reads them.""" if not results: return {}, {} before, after = results reading = PlanReading((), MainTree(Repo.of(repo), revision(repo, head))) - broken = { - cls.key(f"{phase.__name__}::{site}"): 1 + broken: Counts = { + PhaseSiteCount(cls, phase, site): 1 for phase in PlanPhase.members_with(PlanPhase) if phase.__name__ in after - before for site in phase.evidence.sites(reading) or (reading.describe(phase.evidence),) } return dict.fromkeys(broken, 0), broken - @classmethod - def grew(cls, key: str, grown: int) -> str: - phase, _, site = key.partition(":")[2].partition("::") - return f"done plan phase {phase} no longer holds: {site}" - @classmethod def failing(cls, repo: Path, commit: str) -> frozenset[str]: """The name of each phase not done at a commit."""