Skip to content

Commit bee5ab3

Browse files
🔧 build(deps): refresh CI and docs tools (#904)
Co-authored-by: Bernát Gábor <gaborjbernat@gmail.com> Signed-off-by: dependabot[bot] <support@github.com>
1 parent 825f3b5 commit bee5ab3

9 files changed

Lines changed: 136 additions & 44 deletions

‎.github/workflows/codeql.yaml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
with:
2828
persist-credentials: false
2929
- name: "🛡️ Initialize CodeQL"
30-
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
30+
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
3131
with:
3232
languages: ${{ matrix.language }}
3333
build-mode: none
@@ -36,6 +36,6 @@ jobs:
3636
paths-ignore:
3737
- tests
3838
- name: "🛡️ Analyze"
39-
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
39+
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
4040
with:
4141
category: "/language:${{ matrix.language }}"

‎.pre-commit-config.yaml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repos:
99
- id: end-of-file-fixer
1010
- id: trailing-whitespace
1111
- repo: https://github.com/python-jsonschema/check-jsonschema
12-
rev: 0494aa7fa1314e98913a41227d8f0a515b2c1049 # frozen: 0.38.2
12+
rev: a76b92851fc9eede9f9bc4073eb55202f34eb0df # frozen: 0.39.0
1313
hooks:
1414
- id: check-github-workflows
1515
args: ["--verbose"]
@@ -26,7 +26,7 @@ repos:
2626
hooks:
2727
- id: pyproject-fmt
2828
- repo: https://github.com/astral-sh/ruff-pre-commit
29-
rev: "38bfb07c0c64c304f47eb52090f8288150c77730" # frozen: v0.17.1
29+
rev: "25bcf95026b96e2dd3c0aec3090c3d6ef6ca2e6c" # frozen: v0.17.3
3030
hooks:
3131
- id: ruff-format
3232
- id: ruff-check
@@ -56,7 +56,7 @@ repos:
5656
files: ^docs/changelog[.]rst$
5757
args: ["-l", "120"]
5858
additional_dependencies:
59-
- "sphinx==9.1.1"
59+
- "sphinx==9.2.0"
6060
- repo: https://github.com/zizmorcore/zizmor-pre-commit
6161
rev: c92afdf2c6ca336aaa8f39e2bd8d48f62dbd8e1f # frozen: v1.31.1
6262
hooks:

‎tests/soft_rw/test_soft_rw_sync.py‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from functools import partial
1515
from multiprocessing import Event, Process
1616
from pathlib import Path
17+
from types import SimpleNamespace
1718
from typing import TYPE_CHECKING, Any, Final, Literal
1819

1920
import pytest
@@ -942,11 +943,10 @@ def test_writer_evicted_while_draining_queues_behind_its_evictor(
942943
reader = _make_lock(lock_file, heartbeat_interval=10, stale_threshold=40)
943944
reader.acquire_read(timeout=2)
944945
writer = _make_lock(lock_file, heartbeat_interval=10, stale_threshold=40)
945-
real_sleep = time.sleep
946946
swapped = threading.Event()
947947
peer: list[str] = []
948948

949-
def hook(seconds: float) -> None: # ruff:ignore[unused-function-argument] # replaces time.sleep; the duration is irrelevant to the swap
949+
def hook(_seconds: float) -> None:
950950
if swapped.is_set():
951951
raise Timeout(lock_file)
952952
swapped.set()
@@ -959,14 +959,18 @@ def hook(seconds: float) -> None: # ruff:ignore[unused-function-argument] # re
959959
assert log.commit(Snapshot(generation=latest.generation + 1, writer=peer[0], readers=frozenset()))
960960
reader.release()
961961

962-
monkeypatch.setattr(sync_mod.time, "sleep", hook)
962+
monkeypatch.setattr(
963+
sync_mod,
964+
"time",
965+
SimpleNamespace(monotonic=time.monotonic, perf_counter=time.perf_counter, sleep=hook),
966+
)
963967
try:
964968
with pytest.raises(Timeout):
965969
writer.acquire_write(timeout=30)
966970
assert swapped.is_set()
967971
assert _state(lock_file).writer == peer[0]
968972
finally:
969-
monkeypatch.setattr(sync_mod.time, "sleep", real_sleep)
973+
monkeypatch.setattr(sync_mod, "time", time)
970974
writer.close()
971975
reader.close()
972976

@@ -1749,7 +1753,14 @@ def sleep(seconds: float) -> None:
17491753
assert 0 < seconds <= timeout
17501754
real_sleep(seconds)
17511755

1752-
mocker.patch("time.sleep", autospec=True, side_effect=sleep)
1756+
mocker.patch(
1757+
"filelock._soft_rw._sync.time",
1758+
new=SimpleNamespace(
1759+
monotonic=time.monotonic,
1760+
perf_counter=time.perf_counter,
1761+
sleep=mocker.create_autospec(time.sleep, side_effect=sleep),
1762+
),
1763+
)
17531764
with closing(SoftReadWriteLock(lock_file, timeout=timeout, blocking=blocking, poll_interval=10)) as lock:
17541765
acquire: Final = lock.acquire_read if mode == "read" else lock.acquire_write
17551766
with pytest.raises(Timeout) as caught:

‎tests/test_filelock.py‎

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from pkgutil import resolve_name
2626
from queue import Queue
2727
from stat import S_IMODE, S_IWGRP, S_IWOTH, S_IWUSR, filemode
28-
from types import CodeType, FrameType, TracebackType
28+
from types import CodeType, FrameType, SimpleNamespace, TracebackType
2929
from typing import TYPE_CHECKING, Any, Final, Literal, cast, get_args
3030
from uuid import uuid4
3131
from weakref import WeakValueDictionary
@@ -64,6 +64,7 @@
6464

6565
if TYPE_CHECKING:
6666
from collections.abc import Callable, Generator, Iterator
67+
from unittest.mock import Mock
6768

6869
from _typeshed import Unused
6970
from pytest_mock import MockerFixture
@@ -729,48 +730,52 @@ def test_default_poll_interval(lock_type: type[BaseFileLock], tmp_path: Path) ->
729730

730731

731732
@pytest.fixture
732-
def steady_clock(mocker: MockerFixture) -> None:
733+
def steady_clock(mocker: MockerFixture) -> Mock:
733734
# These tests assert the sleep the lock asked for. The lock caps that sleep at the time left before its timeout, so
734735
# a stall on a loaded machine between the start and the first poll would shorten it. A clock that advances 10 ms
735736
# per reading keeps the first poll inside the timeout regardless of load.
736-
mocker.patch("filelock._api.time.perf_counter", side_effect=(reading / 100 for reading in count()))
737+
sleep_mock: Final = mocker.create_autospec(time.sleep)
738+
mocker.patch(
739+
"filelock._api.time",
740+
new=SimpleNamespace(
741+
perf_counter=mocker.create_autospec(time.perf_counter, side_effect=(reading / 100 for reading in count())),
742+
sleep=sleep_mock,
743+
),
744+
)
745+
return sleep_mock
737746

738747

739-
@pytest.mark.usefixtures("steady_clock")
740748
@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
741749
def test_poll_interval_used_by_context_manager(
742-
lock_type: type[BaseFileLock], tmp_path: Path, mocker: MockerFixture
750+
lock_type: type[BaseFileLock], tmp_path: Path, steady_clock: Mock
743751
) -> None:
744752
lock_path = tmp_path / "a"
745753
lock_1 = lock_type(str(lock_path))
746754
lock_2 = lock_type(str(lock_path), timeout=0.2, poll_interval=0.05)
747755

748756
lock_1.acquire()
749-
sleep_mock = mocker.patch("filelock._api.time.sleep")
750757
with pytest.raises(Timeout):
751758
lock_2.acquire()
752-
sleep_mock.assert_any_call(0.05)
759+
steady_clock.assert_any_call(0.05)
753760

754-
sleep_mock.reset_mock()
761+
steady_clock.reset_mock()
755762
lock_2.poll_interval = 0.1
756763
with pytest.raises(Timeout):
757764
lock_2.acquire()
758-
sleep_mock.assert_any_call(0.1)
765+
steady_clock.assert_any_call(0.1)
759766
lock_1.release()
760767

761768

762-
@pytest.mark.usefixtures("steady_clock")
763769
@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
764-
def test_poll_interval_acquire_override(lock_type: type[BaseFileLock], tmp_path: Path, mocker: MockerFixture) -> None:
770+
def test_poll_interval_acquire_override(lock_type: type[BaseFileLock], tmp_path: Path, steady_clock: Mock) -> None:
765771
lock_path = tmp_path / "a"
766772
lock_1 = lock_type(str(lock_path))
767773
lock_2 = lock_type(str(lock_path), timeout=0.2, poll_interval=0.05)
768774

769775
lock_1.acquire()
770-
sleep_mock = mocker.patch("filelock._api.time.sleep")
771776
with pytest.raises(Timeout):
772777
lock_2.acquire(poll_interval=0.15)
773-
sleep_mock.assert_any_call(0.15)
778+
steady_clock.assert_any_call(0.15)
774779
lock_1.release()
775780

776781

@@ -1969,7 +1974,10 @@ def cancel_after_two() -> bool:
19691974
call_count += 1
19701975
return call_count >= 2
19711976

1972-
mocker.patch("filelock._api.time.sleep")
1977+
mocker.patch(
1978+
"filelock._api.time",
1979+
new=SimpleNamespace(perf_counter=time.perf_counter, sleep=mocker.create_autospec(time.sleep)),
1980+
)
19731981
with pytest.raises(Timeout):
19741982
lock_2.acquire(timeout=10, cancel_check=cancel_after_two)
19751983
assert call_count == 2
@@ -3536,7 +3544,8 @@ def test_lock_descriptor_blocking_retries_until_free(
35363544
assert lock_descriptor(holder, blocking=False) is True
35373545
# Drive the real blocking loop: the first attempt sees contention, the mocked sleep frees the holder, and the
35383546
# second attempt wins. Only the clock is mocked, so a single sleep call proves exactly one retry happened.
3539-
sleep = mocker.patch("filelock._descriptor.time.sleep", side_effect=lambda _: unlock_descriptor(holder))
3547+
sleep: Final = mocker.create_autospec(time.sleep, side_effect=lambda _: unlock_descriptor(holder))
3548+
mocker.patch("filelock._descriptor.time", new=SimpleNamespace(sleep=sleep))
35403549
try:
35413550
assert lock_descriptor(fd, blocking=True, poll_interval=poll_interval) is True
35423551
sleep.assert_called_once_with(poll_interval)

‎tests/test_marker_records.py‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import time
66
from contextlib import suppress
77
from multiprocessing import get_context
8+
from types import SimpleNamespace
89
from typing import TYPE_CHECKING, Final
910

1011
import pytest
@@ -166,7 +167,15 @@ def test_lease_never_ages_out_an_unknown_contract(tmp_path: Path) -> None:
166167
@NEEDS_STALE_RECLAIM
167168
def test_lease_ages_out_a_malformed_record(tmp_path: Path, mocker: MockerFixture) -> None:
168169
# The self-heal still applies to a record that states no contract at all, so corruption cannot wedge contenders.
169-
mocker.patch("filelock._soft.time.monotonic", side_effect=itertools.count(step=MALFORMED_LOCK_AGE_THRESHOLD))
170+
mocker.patch(
171+
"filelock._soft.time",
172+
new=SimpleNamespace(
173+
monotonic=mocker.create_autospec(
174+
time.monotonic, side_effect=itertools.count(step=MALFORMED_LOCK_AGE_THRESHOLD)
175+
),
176+
sleep=time.sleep,
177+
),
178+
)
170179
marker = tmp_path / "a.lock"
171180
marker.write_text("filelock/2\nnot-a-record\n", encoding="utf-8")
172181

@@ -286,7 +295,15 @@ def test_marker_lock_reclaims_dead_owner_before_its_process_is_closed(marker: Pa
286295
)
287296
@NEEDS_STALE_RECLAIM
288297
def test_marker_lock_reclaims_malformed_record(marker: Path, record: str, mocker: MockerFixture) -> None:
289-
mocker.patch("filelock._soft.time.monotonic", side_effect=itertools.count(step=MALFORMED_LOCK_AGE_THRESHOLD))
298+
mocker.patch(
299+
"filelock._soft.time",
300+
new=SimpleNamespace(
301+
monotonic=mocker.create_autospec(
302+
time.monotonic, side_effect=itertools.count(step=MALFORMED_LOCK_AGE_THRESHOLD)
303+
),
304+
sleep=time.sleep,
305+
),
306+
)
290307
marker.write_text(record, encoding="utf-8")
291308
lock = MarkerSoftFileLock(marker)
292309
_acquire_after_reclaim(lock)

‎tests/test_read_write_unit.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from concurrent.futures import ThreadPoolExecutor
1010
from contextlib import ExitStack, closing
1111
from functools import partial
12+
from types import SimpleNamespace
1213
from typing import TYPE_CHECKING, Final, Literal
1314

1415
import pytest
@@ -679,7 +680,12 @@ def test_busy_timeout_recomputed_after_journal_mode(
679680
lock_file: str, mocker: MockerFixture, mode: Literal["read", "write"]
680681
) -> None:
681682
counter = iter([0.0, 0.0, 0.0, 0.5])
682-
mocker.patch("filelock._read_write.time.perf_counter", side_effect=counter)
683+
mocker.patch(
684+
"filelock._read_write.time",
685+
new=SimpleNamespace(
686+
monotonic=time.monotonic, perf_counter=mocker.create_autospec(time.perf_counter, side_effect=counter)
687+
),
688+
)
683689
lock = ReadWriteLock(lock_file, is_singleton=False)
684690
(lock.acquire_read if mode == "read" else lock.acquire_write)(timeout=2.0)
685691
lock.release()

‎tests/test_soft_lease.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,13 @@ def test_lease_supersedes_a_live_holder_once_its_claim_ages_out(marker: Path, mo
701701
# lease_duration without a refresh. Windows cannot delete a live holder's open marker, so drive the age branch with
702702
# a non-stale owner and a clock that steps a full duration per look rather than a real second process.
703703
mocker.patch("filelock._lease.owner_is_stale", return_value=False)
704-
mocker.patch("filelock._soft.time.monotonic", side_effect=itertools.count(step=_DURATION))
704+
mocker.patch(
705+
"filelock._soft.time",
706+
new=SimpleNamespace(
707+
monotonic=mocker.create_autospec(time.monotonic, side_effect=itertools.count(step=_DURATION)),
708+
sleep=time.sleep,
709+
),
710+
)
705711
marker.write_text(
706712
f"filelock/2\npid={os.getpid()}\nhost={host_name()}\nmode=lease\ntoken=stalled\nduration={_DURATION!r}\n",
707713
encoding="utf-8",
@@ -839,7 +845,13 @@ def test_native_lock_rejects_a_lease_duration(marker: Path) -> None:
839845
def test_lease_does_not_age_out_a_live_exclusive_owner(marker: Path, mocker: MockerFixture) -> None:
840846
# An exclusive owner never agreed to expire, so only proof of death may reclaim it, however long it sits unchanged.
841847
mocker.patch("filelock._lease.owner_is_stale", return_value=False)
842-
mocker.patch("filelock._soft.time.monotonic", side_effect=itertools.count(step=_DURATION))
848+
mocker.patch(
849+
"filelock._soft.time",
850+
new=SimpleNamespace(
851+
monotonic=mocker.create_autospec(time.monotonic, side_effect=itertools.count(step=_DURATION)),
852+
sleep=time.sleep,
853+
),
854+
)
843855
marker.write_text(f"filelock/2\npid={os.getpid()}\nhost={socket.gethostname()}\nmode=exclusive\n", encoding="utf-8")
844856

845857
with pytest.raises(Timeout):

‎tests/test_soft_stale.py‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from errno import EACCES, EINTR, EINVAL, EIO, ENODEV, ENOSPC, EPERM
1212
from multiprocessing import get_context
1313
from pathlib import Path
14+
from types import SimpleNamespace
1415
from typing import TYPE_CHECKING, Final
1516

1617
import pytest
@@ -1822,7 +1823,10 @@ def test_soft_windows_unlink_gives_up_after_every_attempt_is_denied(tmp_path: Pa
18221823
marker = tmp_path / "a"
18231824
marker.write_text("x", encoding="utf-8")
18241825
lock = SoftFileLock(marker)
1825-
mocker.patch("filelock._soft.time.sleep")
1826+
mocker.patch(
1827+
"filelock._soft.time",
1828+
new=SimpleNamespace(monotonic=time.monotonic, sleep=mocker.create_autospec(time.sleep)),
1829+
)
18261830
unlink: Final = mocker.patch("filelock._soft.unlink_path", side_effect=PermissionError(EACCES, "handle still open"))
18271831

18281832
lock._windows_unlink_if_ours(_file_identity(os.lstat(marker)))
@@ -1838,7 +1842,15 @@ def test_soft_windows_unlink_gives_up_after_every_attempt_is_denied(tmp_path: Pa
18381842
def unchanged_past_grace(mocker: MockerFixture) -> None:
18391843
# Each look at the marker lands a full grace window after the previous one on the observer's clock, so an unchanged
18401844
# malformed marker ages out on the second look without the test sleeping through the window.
1841-
mocker.patch("filelock._soft.time.monotonic", side_effect=itertools.count(step=MALFORMED_LOCK_AGE_THRESHOLD))
1845+
mocker.patch(
1846+
"filelock._soft.time",
1847+
new=SimpleNamespace(
1848+
monotonic=mocker.create_autospec(
1849+
time.monotonic, side_effect=itertools.count(step=MALFORMED_LOCK_AGE_THRESHOLD)
1850+
),
1851+
sleep=time.sleep,
1852+
),
1853+
)
18421854

18431855

18441856
def test_unparseable_lock_with_an_ancient_mtime_not_evicted_on_first_sight(lock_path: Path) -> None:

0 commit comments

Comments
 (0)