File: test_unix_fallback.py

package info (click to toggle)
python-filelock 3.25.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 348 kB
  • sloc: python: 3,851; makefile: 3
file content (113 lines) | stat: -rw-r--r-- 3,448 bytes parent folder | download
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
from __future__ import annotations

import os
import socket
import sys
from errno import EIO, ENOSYS
from typing import TYPE_CHECKING

import pytest

from filelock import SoftFileLock, UnixFileLock

if TYPE_CHECKING:
    from pathlib import Path
    from unittest.mock import MagicMock

    from pytest_mock import MockerFixture

unix_only = pytest.mark.skipif(sys.platform == "win32", reason="unix-only flock fallback")
_ENOSYS_SIDE_EFFECT = OSError(ENOSYS, "Function not implemented")
_FLOCK_PATCH_TARGET = "filelock._unix.fcntl.flock"


@unix_only
def test_fallback_emits_warning(tmp_path: Path, mocker: MockerFixture) -> None:
    mocker.patch(_FLOCK_PATCH_TARGET, side_effect=_ENOSYS_SIDE_EFFECT)
    lock = UnixFileLock(tmp_path / "test.lock")

    with pytest.warns(UserWarning, match="flock not supported on this filesystem, falling back to SoftFileLock"):
        lock.acquire()
    lock.release()


@unix_only
@pytest.mark.filterwarnings("default::UserWarning")
def test_fallback_swaps_to_soft(tmp_path: Path, mocker: MockerFixture) -> None:
    mocker.patch(_FLOCK_PATCH_TARGET, side_effect=_ENOSYS_SIDE_EFFECT)
    lock = UnixFileLock(tmp_path / "test.lock")

    with lock:
        assert lock.is_locked
        assert isinstance(lock, SoftFileLock)


@unix_only
@pytest.mark.filterwarnings("default::UserWarning")
def test_fallback_writes_pid_and_hostname(tmp_path: Path, mocker: MockerFixture) -> None:
    lock_path = tmp_path / "test.lock"
    mocker.patch(_FLOCK_PATCH_TARGET, side_effect=_ENOSYS_SIDE_EFFECT)
    lock = UnixFileLock(lock_path)

    with lock:
        content = lock_path.read_text(encoding="utf-8")
        assert content == f"{os.getpid()}\n{socket.gethostname()}\n"


@unix_only
@pytest.mark.filterwarnings("default::UserWarning")
def test_fallback_release_unlinks_file(tmp_path: Path, mocker: MockerFixture) -> None:
    lock_path = tmp_path / "test.lock"
    mocker.patch(_FLOCK_PATCH_TARGET, side_effect=_ENOSYS_SIDE_EFFECT)
    lock = UnixFileLock(lock_path)

    lock.acquire()
    assert lock_path.exists()
    lock.release()
    assert not lock_path.exists()


@unix_only
@pytest.mark.filterwarnings("default::UserWarning")
def test_fallback_subsequent_acquire_skips_flock(tmp_path: Path, mocker: MockerFixture) -> None:
    flock_mock: MagicMock = mocker.patch(_FLOCK_PATCH_TARGET, side_effect=_ENOSYS_SIDE_EFFECT)
    lock = UnixFileLock(tmp_path / "test.lock")

    lock.acquire()
    lock.release()
    flock_mock.reset_mock()

    lock.acquire()
    lock.release()
    flock_mock.assert_not_called()


@unix_only
@pytest.mark.filterwarnings("default::UserWarning")
def test_fallback_reentrant_locking(tmp_path: Path, mocker: MockerFixture) -> None:
    mocker.patch(_FLOCK_PATCH_TARGET, side_effect=_ENOSYS_SIDE_EFFECT)
    lock = UnixFileLock(tmp_path / "test.lock")

    with lock:
        with lock:
            assert lock.is_locked
        assert lock.is_locked
    assert not lock.is_locked


@unix_only
def test_release_suppresses_eio_on_close(tmp_path: Path, mocker: MockerFixture) -> None:
    lock = UnixFileLock(tmp_path / "test.lock")
    lock.acquire()

    real_close = os.close
    fd_to_fail = lock._context.lock_file_fd

    def _close_eio(fd: int) -> None:
        real_close(fd)
        if fd == fd_to_fail:
            raise OSError(EIO, "Input/output error")

    mocker.patch("filelock._unix.os.close", side_effect=_close_eio)
    lock.release()
    assert not lock.is_locked