3"""Both-direction fixtures for the durable site/binding identity."""
5from __future__
import annotations
7from dataclasses
import replace
8from pathlib
import Path
10from selftest_assert
import expect
11from suppression_identity
import assign_identities
12from suppression_model
import Inventory
13from suppression_scan
import scan_paths
16def _rows(root: Path, files: dict[str, str]) -> dict[tuple[str, int], object]:
17 """Scan an explicit fixture tree and index rows by path and line."""
18 for rel, text
in files.items():
20 target.parent.mkdir(parents=
True, exist_ok=
True)
21 target.write_text(text, encoding=
"ascii")
22 inventory = scan_paths(root, sorted(files))
23 return {(item.path, item.line): item
for item
in inventory.suppressions}
26def _fixture(marker_a: str, marker_b: str, prefix: str =
"", indent: str =
"") -> str:
27 """Compose one two-marker shell fixture with optional prefix lines."""
29 f
"{prefix}#!/bin/sh\n"
30 f
"{indent}probe || true # {marker_a}\n"
32 f
"other || true # {marker_b}\n"
36def assert_identity_semantics(base: Path, failures: list[str]) ->
None:
37 """Assert site identity survives movement and binding tracks content."""
38 left = base /
"identity-a"
39 right = base /
"identity-b"
40 files = {
"tool.sh": _fixture(
"cleanup best effort",
"second reason")}
41 moved = {
"tool.sh": _fixture(
"cleanup best effort",
"second reason", prefix=
"# banner\n")}
42 rows_a = _rows(left, files)
43 rows_b = _rows(right, moved)
44 first_a = rows_a[(
"tool.sh", 2)]
45 first_b = rows_b[(
"tool.sh", 3)]
47 bool(first_a.site_id)
and first_a.site_id == first_b.site_id,
48 "quiet: a line inserted above a site preserves its site_id",
52 first_a.binding_sha256 == first_b.binding_sha256,
53 "quiet: a line inserted above a site preserves its binding",
58 {
"tool.sh": _fixture(
"cleanup best effort",
"second reason", indent=
" ")},
61 indented[(
"tool.sh", 2)].site_id == first_a.site_id,
62 "quiet: indentation-only movement preserves site identity",
65 _assert_identity_content(base, first_a, failures)
68def _assert_identity_content(base: Path, first_a: object, failures: list[str]) ->
None:
69 """Assert reason edits preserve sites while construct edits rename them."""
71 base /
"identity-d", {
"tool.sh": _fixture(
"a different rationale",
"second reason")}
73 changed = reworded[(
"tool.sh", 2)]
75 changed.site_id == first_a.site_id
and changed.binding_sha256 != first_a.binding_sha256,
76 "quiet: reason-only changes preserve the site and invalidate the binding",
80 base /
"identity-reflow",
81 {
"tool.sh":
"#!/bin/sh\n probe || true # reformatted rationale\n"},
84 reflowed.site_id == first_a.site_id,
85 "quiet: reason and whitespace reflow preserve site identity",
90 {
"tool.sh":
"#!/bin/sh\nrewritten_probe || true # cleanup best effort\n"},
93 rewritten[(
"tool.sh", 2)].site_id != first_a.site_id,
94 "must fire: changing the suppressed construct renames the site",
97 _assert_scope_and_directive_rekey(base /
"identity-a", first_a, failures)
98 _assert_repeated_identity(base, failures)
101def _assert_scope_and_directive_rekey(root: Path, original: object, failures: list[str]) ->
None:
102 """Assert approved identity dimensions other than reason still re-key."""
104 replace(original, scope=
"changed-scope", site_id=
"", binding_sha256=
"", anchor=
""),
105 replace(original, directive=
"changed-directive", site_id=
"", binding_sha256=
"", anchor=
""),
107 inventory = Inventory(suppressions=variants)
108 assign_identities(inventory, root)
110 all(item.site_id != original.site_id
for item
in inventory.suppressions),
111 "must fire: scope and directive changes rename the site",
116def _assert_repeated_identity(base: Path, failures: list[str]) ->
None:
117 """Assert ordinals separate repeats without making order semantic."""
120 {
"tool.sh":
"#!/bin/sh\nprobe || true # same\nprobe || true # same\n"},
122 ids = {item.site_id
for item
in repeated.values()}
124 len(repeated) > 1
and len(ids) == len(repeated),
125 "must fire: repeated identical directives stay individually represented",
130 {
"tool.sh":
"#!/bin/sh\nalpha || true # one\nbeta || true # two\n"},
134 {
"tool.sh":
"#!/bin/sh\nbeta || true # two\nalpha || true # one\n"},
137 {item.site_id
for item
in swapped_one.values()}
138 == {item.site_id
for item
in swapped_two.values()},
139 "quiet: reordering distinct sites preserves the identity set",