fix(update): the orphan-marker known failure expires once its fix is in the tree
ORPHAN_MARKER_GAP excused the orphaned_update cell's marker assertion with no expiry. PR CI never runs that cell (Windows, opt-in) nor strict acceptance, so after #132354 (hand-off script names its update child as the line-4 delegate) and #132365 (the judge honours a live delegate) land, a regression of exactly that contract would still pass as an expected failure. Now the fix's footprint is read from the tree (ORPHAN_MARKER_FIX): - while any half is absent, _orphan_gap_excuse applies known_failure as before; - once every half is present the excuse is off, so the cell is strict on any runner whatever the wrapper says; - tests/ci/test_windows_update_crash_oracles.py (runs on every PR) fails with "delete ORPHAN_MARKER_GAP ..." once the fix is in the tree, the same expiry shape as _LANDS_IN_SIBLING_PR, and fails if the Python half already on this branch (update_lock.delegate_live) loses its footprint, so a rename cannot keep the excuse alive. Checked against the scripts branch: this tree + its marker.ps1/windows.ps1 reads no half missing. Review CI1.
This commit is contained in:
@@ -148,3 +148,44 @@ def test_orphan_receipt_is_the_newest_one_written_after_the_baseline(tmp_path):
|
||||
assert crash._new_receipt_outcome(machine, before) is None # an earlier run's success is not this run's
|
||||
(receipts / "update_20261004_000100_2_b.json").write_text('{"outcome": "failed"}', encoding="utf-8")
|
||||
assert crash._new_receipt_outcome(machine, before) == "failed"
|
||||
|
||||
|
||||
# Review CI1: a run-time xfail outlives its fix unless something checks the tree. PR CI never runs
|
||||
# the orphan cell (Windows, opt-in) nor strict acceptance, so this file is the expiry.
|
||||
def test_orphan_marker_wrapper_expires_once_its_fix_is_in_the_tree():
|
||||
missing = crash.orphan_marker_fix_missing()
|
||||
assert missing, ("the orphan-marker fix (#132354 + #132365) is in the tree: delete ORPHAN_MARKER_GAP, "
|
||||
"ORPHAN_MARKER_FIX, _orphan_gap_excuse and these guards, and assert the marker plainly")
|
||||
# The halves already on this branch must still carry their footprint: a renamed judge would
|
||||
# otherwise read as "not landed" forever and keep the excuse alive.
|
||||
landed = {rel for rel, _ in crash.ORPHAN_MARKER_FIX} - {m.split(":")[0] for m in missing}
|
||||
assert "hermes_cli/update_lock.py" in landed, f"the Python delegate judge lost its footprint: {missing}"
|
||||
|
||||
|
||||
def test_orphan_marker_fix_footprint_is_read_from_the_tree(tmp_path):
|
||||
for rel, footprint in crash.ORPHAN_MARKER_FIX:
|
||||
(tmp_path / rel).parent.mkdir(parents=True, exist_ok=True)
|
||||
(tmp_path / rel).write_text(f"x\n{footprint}\n", encoding="utf-8")
|
||||
assert crash.orphan_marker_fix_missing(tmp_path) == []
|
||||
(tmp_path / "scripts/desktop-update/windows.ps1").unlink()
|
||||
assert crash.orphan_marker_fix_missing(tmp_path) == [
|
||||
"scripts/desktop-update/windows.ps1: " + crash.ORPHAN_MARKER_FIX[2][1]]
|
||||
|
||||
|
||||
_GAP_FAILURE = "orphaned_update: .hermes-update-in-progress read DEAD 3s after the script died"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fix_in_tree", [False, True])
|
||||
def test_orphan_gap_is_excused_only_while_its_fix_is_absent(monkeypatch, fix_in_tree):
|
||||
monkeypatch.delenv("HERMES_E2E_STRICT_ACCEPTANCE", raising=False)
|
||||
monkeypatch.setattr(crash, "orphan_marker_fix_missing", lambda: [] if fix_in_tree else ["a half"])
|
||||
expected = AssertionError if fix_in_tree else pytest.xfail.Exception
|
||||
with pytest.raises(expected, match="read DEAD"):
|
||||
with crash._orphan_gap_excuse():
|
||||
raise AssertionError(_GAP_FAILURE)
|
||||
|
||||
|
||||
def test_orphan_marker_guard_fires_once_the_fix_lands(monkeypatch):
|
||||
monkeypatch.setattr(crash, "orphan_marker_fix_missing", lambda: [])
|
||||
with pytest.raises(AssertionError, match="delete ORPHAN_MARKER_GAP"):
|
||||
test_orphan_marker_wrapper_expires_once_its_fix_is_in_the_tree()
|
||||
|
||||
@@ -756,6 +756,33 @@ def test_desktop_handoff_killed_mid_run_leaves_a_runnable_install(journey: Journ
|
||||
# (HERMES_E2E_STRICT_ACCEPTANCE=upd-txn) fails on it instead.
|
||||
ORPHAN_MARKER_GAP = (r"orphaned_update: \.hermes-update-in-progress (read DEAD|survived)",
|
||||
"upd-txn: the line-4 delegate lands in #132354 + #132365")
|
||||
# The expiry PR CI can check (it never runs this cell, nor strict): the fix's footprint in the
|
||||
# tree. Once every half is here the excuse is off, so the cell is strict on any runner, and
|
||||
# tests/ci/test_windows_update_crash_oracles.py (every PR) fails until the wrapper is deleted.
|
||||
ORPHAN_MARKER_FIX = (
|
||||
("hermes_cli/update_lock.py", "def delegate_live("), # #132365: the judge honours a live delegate
|
||||
("scripts/desktop-update/marker.ps1", "function Add-MarkerDelegate("), # #132354: the script names one
|
||||
("scripts/desktop-update/windows.ps1", "Add-MarkerDelegate @($proc.Id)"), # ... its update child
|
||||
)
|
||||
_REPO = Path(__file__).resolve().parents[4]
|
||||
|
||||
|
||||
def orphan_marker_fix_missing(root: Path = _REPO) -> list[str]:
|
||||
"""The halves of the orphan-marker fix absent from ``root`` (``[]``: the gap is closed there)."""
|
||||
missing = []
|
||||
for rel, footprint in ORPHAN_MARKER_FIX:
|
||||
try:
|
||||
text = (root / rel).read_text(encoding="utf-8-sig", errors="replace")
|
||||
except FileNotFoundError:
|
||||
text = ""
|
||||
if footprint not in text:
|
||||
missing.append(f"{rel}: {footprint}")
|
||||
return missing
|
||||
|
||||
|
||||
def _orphan_gap_excuse() -> contextlib.AbstractContextManager[None]:
|
||||
"""``known_failure`` for the orphan-marker gap while its fix is absent; nothing once it is here."""
|
||||
return known_failure(*ORPHAN_MARKER_GAP) if orphan_marker_fix_missing() else contextlib.nullcontext()
|
||||
|
||||
|
||||
def test_desktop_handoff_script_killed_alone_keeps_the_marker_live_until_its_update_ends(
|
||||
@@ -786,5 +813,5 @@ def test_desktop_handoff_script_killed_alone_keeps_the_marker_live_until_its_upd
|
||||
f"hermes update still ran: {r['dead_while_running'][1]} (at kill: {r['marker_at_kill']!r})")
|
||||
if r["marker_after_orphan"] is not None:
|
||||
gaps.append(f"{MARKER} survived the orphaned update's exit: {r['marker_after_orphan_text']}")
|
||||
with known_failure(*ORPHAN_MARKER_GAP):
|
||||
with _orphan_gap_excuse():
|
||||
assert not gaps, fail_with(m, "orphaned_update: " + "; ".join(gaps))
|
||||
|
||||
Reference in New Issue
Block a user