Task · SFL-0057

tests: pin the file-lease coordinator's write-failure, verify-read-error, renewal-tolerance and corrupt-lease branches

Status
To Do
Labels
followup, phase-1
Milestone
Test-coverage backlog
Updated
2026-08-14

Description

What

src/sf2loki/coordinate/file_lease.py sits at 87% statement coverage (189 statements, 25 missed) across the whole test suite. Several of the missed statements are the failure-handling branches that make the HA lease safe, and every one of them would survive a regression that deleted or inverted it.

Measured with coverage run --source=src/sf2loki/coordinate -m pytest tests over the full suite (1045 passed): missing lines 66, 200, 210-214, 216, 219-220, 236, 273, 276-277, 300, 311, 348, 352, 355-356, 358, 376-379.

The behaviour-bearing gaps:

The remaining missed lines are trivia and not the point of this issue: 66 (_default_utcnow, always injected in tests), 200/216/236/273/311 (stop-event early returns in _acquire/_hold/_pause), 376-379 (the tmp-file cleanup in _write).

Why it matters

App._run does not absorb exceptions from the coordinator: await self._coordinator.run(...) at src/sf2loki/app.py:1210 sits in a try/finally whose only job is resource shutdown, and a pipeline crash is re-raised at src/sf2loki/app.py:1224. So each of these branches is the difference between a bounded retry and a process exit.

Proposed approach

All additions go in tests/coordinate/test_file_lease.py using the existing FakeClock / ScriptedSleep harness (no real sleeps, injected clock). Config default from _cfg is ttl=30, renew=10, so _verify_delay is 1.0s; sleep call ordering is what the scripted actions key off.

  1. Acquire retries after a write failure (covers 210-214). Absent lease; wrap _write so the first call raises OSError("shared storage unreachable") and later calls delegate to the real implementation. Sleep order: #0 = the post-failure renew-interval back-off, #1 = the verification delay on the successful retry. Assert await coord._acquire(stop) is True, _read_holder(path) == "B", and that _write was attempted twice.
  2. Acquire treats an unreadable verification read as a lost race (covers 219-220). Absent lease so the contest proceeds; stub _read to return None on call #1 and raise _LeaseReadError("transient NFS error") on call #2. Script sleep #1 (the loser back-off) to stop.set(). Assert _acquire returns False and coord.epoch == 0 — a lease it could not confirm never counts as won.
  3. Hold tolerates an unreadable verify read and still renews (covers 276-277). B holds (coord._write(clock.now, 1), coord._epoch = 1); stub _read to return None on call #1 (pre-renew) and raise _LeaseReadError on call #2 (the verify read). Sleeps: #0 renew pause, #1 verify pause, #2 stop.set(). Assert _hold returned only because stop fired (stop.is_set()), _read_holder(path) == "B", and the persisted epoch is still 1.
  4. Hold keeps leadership through a sub-ttl renewal failure (covers 300). B holds at _BASE, last_ok = _BASE. Sleeps: #0 advances the clock 10s (< ttl 30), #1 advances another 10s (cumulative 20s, still < ttl), #2 stop.set(). _write raises OSError on the first renewal attempt only, then delegates to the real write. Assert _hold did not surrender early (stop.is_set() is what ended it), the lease on disk still has holder == "B", its expires_at advanced past the pre-failure value, and the epoch is preserved. This test must fail if line 300’s log.warning is replaced by return.
  5. Parametrized _read corrupt-content matrix (covers 348, 352, 355-356, 358, plus the non-int epoch arm at 360). Write raw text into the lease file and call coord._read() directly:
    • "[1, 2]"None (348)
    • {"holder": 1, "expires_at": "<aware iso>"}None (352)
    • {"holder": "A", "expires_at": 5}None (352)
    • {"holder": "A", "expires_at": "not-a-timestamp"}None (355-356)
    • {"holder": "A", "expires_at": "2026-01-01T12:00:30"} (naive) → lease returned with expires_at == datetime(2026, 1, 1, 12, 0, 30, tzinfo=UTC), and lease.expired(_BASE) is False so the aware/naive comparison is exercised rather than just the field value (358)
    • {"holder": "A", "expires_at": "<aware iso>", "epoch": "x"}lease.epoch == 0 (360’s non-int arm)

Follow the file’s existing convention of monkeypatching coord._read / coord._write on the instance with # type: ignore[assignment]; keep mypy --strict clean.


Imported from GitHub issue #141 on 2026-08-14, when this repo migrated from GitHub Issues to Backlog.md. The original issue has been deleted; its verbatim body, labels and comments are preserved in archive/issues-dump.json (jq '.[] | select(.number == 141)' archive/issues-dump.json).

Filed from the 2026-07-30 full-repo audit (11 finder lanes + adversarial verification per finding).

Acceptance Criteria

Definition of Done

References

View the source file on GitHub