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
16 changes: 13 additions & 3 deletions scripts/family.py
Original file line number Diff line number Diff line change
Expand Up @@ -655,10 +655,20 @@ def rewritten(self, rows: list[dict]) -> str:
EVERY SUCH LINE IS COUNTED FIRST, and anything but one is refused
(#920, lane 5's review of #957, SHOULD 1). The rewrite stopped at the
first and never counted a second, so a `members: []` above `name:`,
with the real block still at the end, which the reader takes as the
last of two keys and the holder's gate passes, was cut off there with
exit 0: every key below it went, and the gate then failed the
with the real block still at the end, which the reader then took as
the last of two keys and the holder's gate passed, was cut off there
with exit 0: every key below it went, and the gate then failed the
commit. With the null words found, `members: null` did the same.

NO VERB REACHES A COUNT OF TWO since #974 (#1029). The reader refuses
a key written twice, by the line of the second, so `Family.open`
stops such a manifest as `yaml-unparsable` before any verb asks this.
The count stays as defence in depth, for a `family.yaml` changed
between the open and the rewrite, a caller of this method, or a
reader relaxed later, and it is asked of the method itself
(`test_two_members_lines_are_refused_by_the_rewrite_itself`). A
count of none is still reached through a verb: a comment on the
line, or no block at all.
"""
text = self.path.read_text(encoding="utf-8")
lines = text.splitlines()
Expand Down
34 changes: 33 additions & 1 deletion tests/test_family_add_gitlink_and_null_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,36 @@ def test_a_word_the_reader_refuses_is_no_members_line(tmp_path,
assert held.rewritten([]) == text


@pytest.mark.parametrize("line", ["members: null", "members: []"])
def test_two_members_lines_are_refused_by_the_rewrite_itself(
tmp_path, family_module, line):
"""THE COUNT OF TWO, ASKED OF THE METHOD (#1029, lane 4's RV1026,
SHOULD 1). Since #974 the reader refuses a key written twice, so a verb
stops such a manifest at `Family.open` (the premise below) and
`test_a_second_members_line_above_the_block_is_refused` never reaches
this count. It is defence in depth, for a manifest changed after the
open or a caller of the method: a second margin `members:` line above
`name:` is refused with both lines counted, and nothing is rewritten,
rather than the manifest cut off at the first one and every key below
it lost."""
path = tmp_path / "family.yaml"
text = f"kind: family-manifest\n{line}\nname: x\nmembers: []\n"
path.write_text(text, encoding="utf-8")
with pytest.raises(family_module.Refusal) as read:
family_module.load_yaml(path)
assert (read.value.code, read.value.detail) == (
"yaml-unparsable",
f"{path}: line 4: key 'members' is written twice"), "premise"
held = family_module.Family(tmp_path, {"members": []})
with pytest.raises(family_module.Refusal) as caught:
held.write_members([])
assert caught.value.code == "family-members-unrecognised"
assert caught.value.detail == (
f"{path} has 2 `members:` line(s) where exactly one was expected, "
"so this tool will not rewrite it")
assert path.read_text(encoding="utf-8") == text


def a_line_above_the_name(root: Path, line: str) -> None:
"""`line` at the margin above `family.yaml`'s `name:`, with the real
`members:` block still at the end, committed, as a person commits a
Expand All @@ -489,7 +519,9 @@ def test_a_second_members_line_above_the_block_is_refused(family, holder,
written twice, by the line of the second, so `add` stops at
`Family.open`, before the mount and before any line is counted, and
the file is untouched. The refusal is the reader's own `yaml-unparsable`:
one line, with no remediation of its own."""
one line, with no remediation of its own. The count of two itself is
asked of the method, #1029:
`test_two_members_lines_are_refused_by_the_rewrite_itself`."""
a_line_above_the_name(holder, line)
opens = [number for number, text in enumerate(
(holder / "family.yaml").read_text(encoding="utf-8").splitlines(), 1)
Expand Down
13 changes: 12 additions & 1 deletion tests/test_family_add_refusals.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,16 @@ def snapshot(root: Path, *, status: bool = True) -> dict:
"""Everything a refused `add` must leave exactly as it found it.

`status=False` for a holder with no `git status` to ask: one whose HEAD
names a missing object answers `fatal: bad object HEAD`."""
names a missing object answers `fatal: bad object HEAD`.

EVERY REF, NOT HEAD ALONE (#1029, lane 4's RV1026, NIT 2). A refusal
that left a branch behind, made before a later step refused, kept every
test built on `assert_unchanged` green: RV1026's mutant that makes
`refs/heads/rv-mutant` in `Family.open`'s refused parse passed all 25
rows of `test_family_add_gitlink_and_null_members.py`. Each ref is
recorded by name and object, and only those two fields, so a ref whose
object is missing is recorded as it is rather than failing the
snapshot."""
def read(name: str) -> bytes | None:
target = root / name
return target.read_bytes() if target.is_file() else None
Expand All @@ -160,6 +169,8 @@ def listing(directory: Path) -> list[str] | None:
# rather than failing the snapshot.
"HEAD": git("rev-parse", "-q", "--verify", "HEAD", cwd=root,
check=False).stdout,
"refs": git("for-each-ref", "--format=%(refname) %(objectname)",
cwd=root).stdout,
"index": git("ls-files", "-s", cwd=root).stdout,
"status": (git("status", "--porcelain", "--ignored", cwd=root).stdout
if status else None),
Expand Down
Loading