File: test_lock_expiry.py

package info (click to toggle)
python-filelock 3.24.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 328 kB
  • sloc: python: 3,513; makefile: 3
file content (144 lines) | stat: -rw-r--r-- 4,359 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
from __future__ import annotations

import os
import time
from typing import TYPE_CHECKING

import pytest

from filelock import FileLock, SoftFileLock

if TYPE_CHECKING:
    from pathlib import Path

    from pytest_mock import MockerFixture


@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
def test_expired_lock_is_broken(lock_type: type[FileLock | SoftFileLock], tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    lock_path.touch()
    os.utime(lock_path, (0, 0))

    lock = lock_type(lock_path, lifetime=0.1, timeout=1)
    with lock:
        assert lock.is_locked


def test_soft_non_expired_lock_not_broken(tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    lock_path.touch()

    lock = SoftFileLock(lock_path, lifetime=9999, timeout=0.2)
    with pytest.raises(TimeoutError):
        lock.acquire()


def test_soft_lifetime_none_no_expiry(tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    lock_path.touch()
    os.utime(lock_path, (0, 0))

    lock = SoftFileLock(lock_path, lifetime=None, timeout=0.2)
    with pytest.raises(TimeoutError):
        lock.acquire()


def test_expired_lock_race_rename_fails(tmp_path: Path, mocker: MockerFixture) -> None:
    lock_path = tmp_path / "test.lock"
    lock_path.touch()
    os.utime(lock_path, (0, 0))

    mocker.patch("filelock._api.pathlib.Path.rename", side_effect=FileNotFoundError)

    lock = SoftFileLock(lock_path, lifetime=0.1, timeout=0.5)
    with pytest.raises(TimeoutError):
        lock.acquire()


def test_lifetime_property_getter_setter(tmp_path: Path) -> None:
    lock = FileLock(tmp_path / "test.lock", lifetime=10.0)
    assert lock.lifetime == pytest.approx(10.0)

    lock.lifetime = 20.0
    assert lock.lifetime == pytest.approx(20.0)

    lock.lifetime = None
    assert lock.lifetime is None


def test_lifetime_default_none(tmp_path: Path) -> None:
    lock = FileLock(tmp_path / "test.lock")
    assert lock.lifetime is None


def test_lifetime_singleton_mismatch(tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    lock1 = FileLock(lock_path, is_singleton=True, lifetime=10.0)
    assert lock1.lifetime == pytest.approx(10.0)

    with pytest.raises(ValueError, match="lifetime"):
        FileLock(lock_path, is_singleton=True, lifetime=20.0)


def test_lifetime_singleton_match(tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    lock1 = FileLock(lock_path, is_singleton=True, lifetime=10.0)
    lock2 = FileLock(lock_path, is_singleton=True, lifetime=10.0)
    assert lock1 is lock2


@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
def test_lock_file_missing_during_expiry_check(lock_type: type[FileLock | SoftFileLock], tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"

    lock = lock_type(lock_path, lifetime=0.1, timeout=1)
    with lock:
        assert lock.is_locked


@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
def test_expired_lock_becomes_acquirable(lock_type: type[FileLock | SoftFileLock], tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    lock_path.touch()
    os.utime(lock_path, (0, 0))

    lock = lock_type(lock_path, lifetime=0.5, timeout=1)
    with lock:
        assert lock.is_locked
    assert not lock.is_locked


@pytest.mark.asyncio
async def test_async_expired_lock_is_broken(tmp_path: Path) -> None:
    from filelock import AsyncFileLock

    lock_path = tmp_path / "test.lock"
    lock_path.touch()
    os.utime(lock_path, (0, 0))

    lock = AsyncFileLock(lock_path, lifetime=0.1, timeout=1)
    async with lock:
        assert lock.is_locked


@pytest.mark.asyncio
async def test_async_soft_non_expired_lock_not_broken(tmp_path: Path) -> None:
    from filelock import AsyncSoftFileLock

    lock_path = tmp_path / "test.lock"
    lock_path.touch()

    lock = AsyncSoftFileLock(lock_path, lifetime=9999, timeout=0.2)
    with pytest.raises(TimeoutError):
        await lock.acquire()


@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
def test_lock_mtime_updated_on_acquire(lock_type: type[FileLock | SoftFileLock], tmp_path: Path) -> None:
    lock_path = tmp_path / "test.lock"
    before = time.time()
    lock = lock_type(lock_path, lifetime=60)
    with lock:
        if lock_path.exists():
            assert lock_path.stat().st_mtime >= before - 1