import multiprocessing as mp import time from pathlib import Path import pytest from utils.file_lock import exclusive_lock def _hold_lock(path: Path, hold_time: float, started: mp.Event): with exclusive_lock(path): started.set() time.sleep(hold_time) def _try_lock(path: Path, wait_time: mp.Value): t0 = time.perf_counter() with exclusive_lock(path): wait_time.value = time.perf_counter() - t0 def test_exclusive_lock_blocks_until_released(tmp_path: Path) -> None: file_path = tmp_path / "locktest.txt" # Use 'fork' start method when available for more deterministic timing on # platforms like macOS where the default 'spawn' method can delay process # startup significantly. if "fork" in mp.get_all_start_methods(): ctx = mp.get_context("fork") else: ctx = mp.get_context() started = ctx.Event() wait_time = ctx.Value("d", 0.0) p1 = ctx.Process(target=_hold_lock, args=(file_path, 1.0, started)) p2 = ctx.Process(target=_try_lock, args=(file_path, wait_time)) p1.start() started.wait() time.sleep(0.1) p2.start() p1.join() p2.join() # CI runners can be jittery; allow generous slack around the 1s lock hold time # Different operating systems spawn processes at slightly different speeds # which can shift the measured wait time by a few hundred milliseconds. A # wider tolerance keeps the test stable across platforms. assert wait_time.value == pytest.approx(1.0, abs=0.5)