from __future__ import annotations import pytest from host_agent.instance_lock import ( InstanceAlreadyRunningError, InstanceLock, ) def test_acquire_succeeds_when_free(tmp_path) -> None: lock = InstanceLock(tmp_path) lock.acquire() assert lock.lock_path == tmp_path / "host_agent.lock" lock.release() def test_acquire_raises_when_already_held(tmp_path) -> None: first = InstanceLock(tmp_path) first.acquire() try: second = InstanceLock(tmp_path) with pytest.raises(InstanceAlreadyRunningError) as info: second.acquire() assert info.value.lock_path == tmp_path / "host_agent.lock" assert "another Host Agent instance" in str(info.value) finally: first.release() def test_release_then_reacquire_succeeds(tmp_path) -> None: lock = InstanceLock(tmp_path) lock.acquire() lock.release() # Same handle can reacquire after release. lock.acquire() lock.release() # A fresh handle against the same path also succeeds. other = InstanceLock(tmp_path) other.acquire() other.release() def test_release_is_idempotent_and_safe_before_acquire(tmp_path) -> None: lock = InstanceLock(tmp_path) # Releasing before acquire must be a no-op, not raise. lock.release() lock.acquire() lock.release() lock.release() def test_independent_paths_never_contend(tmp_path) -> None: dir_a = tmp_path / "identity-a" dir_b = tmp_path / "identity-b" lock_a = InstanceLock(dir_a) lock_a.acquire() try: lock_b = InstanceLock(dir_b) lock_b.acquire() lock_b.release() finally: lock_a.release() def test_context_manager_releases_on_exit(tmp_path) -> None: with InstanceLock(tmp_path): second = InstanceLock(tmp_path) with pytest.raises(InstanceAlreadyRunningError): second.acquire() # After the context exits, a fresh handle can acquire. InstanceLock(tmp_path).acquire() def test_state_directory_is_created_if_missing(tmp_path) -> None: nested = tmp_path / "nested" / "state" lock = InstanceLock(nested) assert nested.exists() lock.acquire() lock.release()