mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:27:13 +01:00
Some checks are pending
pre-commit / pre-commit (push) Waiting to run
Tests / test (3.10) (push) Waiting to run
Tests / test (3.11) (push) Waiting to run
Tests / test (3.12) (push) Waiting to run
Tests / lock-popup-safety (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
A prior commit (6a7a7d8) accidentally captured only the _constants.py
half of an already-in-progress, uncommitted change removing
RUNNERUP_ACCEPTED_SPORTS -- leaving main broken (import error) since
_runnerup_verification.py and its 15 test files still referenced the
now-undefined constant. Committing the matching half: RunnerUp
entries are now judged purely by distance/duration, any sport
qualifies, matching how every other workout type is already treated.
Also: the new single-instance lock was not test-isolated, so every
ScreenLocker built across the suite contended for the same real
$XDG_RUNTIME_DIR/gatelock file -- only the first test won it, the
rest silently exited. Added to conftest.py's isolated-state table
(split into tests/_isolated_state.py to stay under the 250-line cap)
and added dedicated coverage for screen_locker/_instance.py.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M5gKyYzxWG6QkCaj1wWfk
97 lines
2.4 KiB
Python
97 lines
2.4 KiB
Python
"""Tests for the single-instance lock."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import TYPE_CHECKING
|
|
|
|
from screen_locker import _instance
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def test_the_first_run_takes_the_lock(tmp_path: Path) -> None:
|
|
lock = _instance.acquire(tmp_path / "instance.lock")
|
|
|
|
assert lock is not None
|
|
lock.release()
|
|
|
|
|
|
def test_a_second_run_stands_down(
|
|
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
"""Otherwise a stray re-arm races a second window into existence."""
|
|
path = tmp_path / "instance.lock"
|
|
first = _instance.acquire(path)
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
second = _instance.acquire(path)
|
|
|
|
assert first is not None
|
|
assert second is None
|
|
assert any("already holds" in record.message for record in caplog.records)
|
|
first.release()
|
|
|
|
|
|
def test_the_lock_is_retakeable_after_release(tmp_path: Path) -> None:
|
|
path = tmp_path / "instance.lock"
|
|
first = _instance.acquire(path)
|
|
assert first is not None
|
|
first.release()
|
|
|
|
second = _instance.acquire(path)
|
|
|
|
assert second is not None
|
|
second.release()
|
|
|
|
|
|
def test_an_unopenable_path_reports_failure_rather_than_raising(
|
|
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
blocker = tmp_path / "blocker"
|
|
blocker.write_text("file", encoding="utf-8")
|
|
|
|
with caplog.at_level(logging.ERROR):
|
|
assert _instance.acquire(blocker / "instance.lock") is None
|
|
|
|
|
|
def test_releasing_twice_is_safe(tmp_path: Path) -> None:
|
|
lock = _instance.acquire(tmp_path / "instance.lock")
|
|
assert lock is not None
|
|
|
|
lock.release()
|
|
lock.release()
|
|
|
|
|
|
def test_the_lock_file_records_the_pid(tmp_path: Path) -> None:
|
|
path = tmp_path / "instance.lock"
|
|
lock = _instance.acquire(path)
|
|
assert lock is not None
|
|
|
|
assert path.read_text(encoding="utf-8") == str(os.getpid())
|
|
lock.release()
|
|
|
|
|
|
def test_a_close_failure_is_reported_not_raised(
|
|
tmp_path: Path, caplog: pytest.LogCaptureFixture
|
|
) -> None:
|
|
lock = _instance.acquire(tmp_path / "instance.lock")
|
|
assert lock is not None
|
|
|
|
class Broken:
|
|
def close(self) -> None:
|
|
message = "cannot close"
|
|
raise OSError(message)
|
|
|
|
lock.handle.close()
|
|
lock.handle = Broken()
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
lock.release()
|
|
|
|
assert any("could not close" in record.message for record in caplog.records)
|