diff --git a/tests/test_fence_contention.py b/tests/test_fence_contention.py index e30779a..c60406e 100644 --- a/tests/test_fence_contention.py +++ b/tests/test_fence_contention.py @@ -18,9 +18,9 @@ itself (via `a._flock()`) while a child process attempts `acquire()` on the same lock/journal, and we assert on wall-clock time that the child was genuinely blocked. The second test is the control: with the - child's `_flock()` neutered, it returns fast -- proving the first - test would fail (not pass vacuously) if `_flock()`'s exclusion were - ever removed from `fence.py`. + child's `_flock()` neutered, it must finish while the parent still holds + the lock. This confirms the child can otherwise acquire without waiting + for the parent's release, without a machine-speed threshold. """ from __future__ import annotations import select, subprocess, sys, textwrap, time @@ -140,25 +140,22 @@ def test_child_process_blocks_on_flock_while_parent_holds_it(tmp_path): def test_child_process_without_flock_does_not_block_control(tmp_path): # R1's control: with the CHILD's `_flock()` neutered to a no-op (while # the parent still genuinely holds the OS lock), the child never - # contends and returns fast. This proves the previous test is - # discriminating -- if `_flock()`'s exclusion were ever removed from - # `fence.py` for real, that test's >= 0.4s assertion would fail rather - # than passing vacuously. + # contends and completes before the parent releases its lock. Holding + # that lock throughout subprocess.run is the control; a fixed sub-second + # latency threshold instead measures scheduler/filesystem speed on CI. j_path = tmp_path / "j.sqlite3" lock = tmp_path / "j.lock" j = Journal.open(j_path) a = FencedLease(lock, j) child_code = textwrap.dedent(f""" - import contextlib, time + import contextlib from pineforge_live.journal import Journal, FencedLease FencedLease._flock = lambda self: contextlib.nullcontext() j = Journal.open({str(j_path)!r}, create=False) b = FencedLease({str(lock)!r}, j) - t0 = time.monotonic() token = b.acquire(lease_ms=1_000, now_ms=0) - elapsed = time.monotonic() - t0 - print(f"ACQUIRED:{{token}}:{{elapsed:.3f}}") + print(f"ACQUIRED:{{token}}") j.close() """) @@ -166,8 +163,6 @@ def test_child_process_without_flock_does_not_block_control(tmp_path): result = subprocess.run( [sys.executable, "-c", child_code], capture_output=True, text=True, timeout=15, ) - assert result.returncode == 0, result.stderr - marker, token_str, elapsed_str = result.stdout.strip().split(":") - assert marker == "ACQUIRED", (result.stdout, result.stderr) - assert float(elapsed_str) < 0.2, f"child should not have blocked (flock neutered): {result.stdout}" + assert result.returncode == 0, result.stderr + assert result.stdout.strip() == "ACQUIRED:1", (result.stdout, result.stderr) j.close() diff --git a/tests/test_journal.py b/tests/test_journal.py index 69f8fcb..e8c9606 100644 --- a/tests/test_journal.py +++ b/tests/test_journal.py @@ -576,24 +576,24 @@ def test_prepare_pad_races_a_concurrent_write_without_disarming_it(tmp_path, mon # file, so the worst case is a harmless over-length file. m = StopMarker(tmp_path / "j.sqlite3.stop") m.path.write_bytes(b"") # short/armed, as in the R3 crash scenario - real_stat = Path.stat + real_open = os.open raced = [] - def racing_stat(self, *a, **kw): - # prepare()'s only Path.stat() call on this file is its explicit - # size check (_payload()'s exists() check uses os.path.exists(), - # not Path.stat(), on this Python version) -- capture the OLD - # (short) size first, exactly as the real call would, THEN inject - # the race so the returned size is stale relative to the file the - # subsequent pad write actually sees. - result = real_stat(self, *a, **kw) - if self == m.path and not raced: + def racing_open(path, flags, *a, **kw): + # Inject after prepare() captured the short size but before it opens + # the padding descriptor. Path.exists() calls Path.stat() on some + # Python versions, so a Path.stat hook can instead set the marker + # before the initial payload check and miss the intended race. + if os.fspath(path) == os.fspath(m.path) and flags & os.O_APPEND and not raced: raced.append(True) StopMarker(m.path).write("HARD", "HOLD", "raced in") - return result - monkeypatch.setattr(Path, "stat", racing_stat) + return real_open(path, flags, *a, **kw) + monkeypatch.setattr(os, "open", racing_open) m.prepare() + from pineforge_live.journal.sidecar import SIZE + assert raced == [True] + assert m.path.stat().st_size == 2 * SIZE # STOP payload, then stale-size pad. assert m.exists() # the race's SET marker survived the pad -- not disarmed assert m.read()["cause"] == "raced in"