diff --git a/docs/changelog.rst b/docs/changelog.rst index 176b9944..c760d183 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,5 +1,9 @@ Changelog ========= +v3.10.6 (2023-03-25) +-------------------- +- Enhance the robustness of the try/catch block in _soft.py. by :user:`jahrules`. + v3.10.5 (2023-03-25) -------------------- - Add explicit error check as certain UNIX filesystems do not support flock. by :user:`jahrules`. diff --git a/src/filelock/_soft.py b/src/filelock/_soft.py index 6d953407..697f6c4c 100644 --- a/src/filelock/_soft.py +++ b/src/filelock/_soft.py @@ -2,7 +2,7 @@ import os import sys -from errno import EACCES, EEXIST, ENOENT +from errno import EACCES, EEXIST from ._api import BaseFileLock from ._util import raise_on_exist_ro_file @@ -21,17 +21,15 @@ def _acquire(self) -> None: | os.O_TRUNC # truncate the file to zero byte ) try: - fd = os.open(self._lock_file, flags, self._mode) - except OSError as exception: - if exception.errno == EEXIST: # expected if cannot lock - pass - elif exception.errno == ENOENT: # No such file or directory - parent directory is missing + file_handler = os.open(self._lock_file, flags, self._mode) + except OSError as exception: # re-raise unless expected exception + if not ( + exception.errno == EEXIST # lock already exist + or (exception.errno == EACCES and sys.platform == "win32") # has no access to this lock + ): # pragma: win32 no cover raise - elif exception.errno == EACCES and sys.platform != "win32": # pragma: win32 no cover - # Permission denied - parent dir is R/O - raise # note windows does not allow you to make a folder r/o only files else: - self._lock_file_fd = fd + self._lock_file_fd = file_handler def _release(self) -> None: os.close(self._lock_file_fd) # type: ignore # the lock file is definitely not None diff --git a/tests/test_filelock.py b/tests/test_filelock.py index ef8d59db..e635dd5d 100644 --- a/tests/test_filelock.py +++ b/tests/test_filelock.py @@ -185,7 +185,7 @@ def __init__(self, target: Callable[[], None], name: str) -> None: def run(self) -> None: try: super().run() - except Exception: # pragma: no cover + except RuntimeError: # pragma: no cover self.ex = sys.exc_info() # pragma: no cover def join(self, timeout: float | None = None) -> None: @@ -337,8 +337,8 @@ def test_context_release_on_exc(lock_type: type[BaseFileLock], tmp_path: Path) - with lock as lock_1: assert lock is lock_1 assert lock.is_locked - raise Exception - except Exception: + raise ValueError + except ValueError: assert not lock.is_locked @@ -352,8 +352,8 @@ def test_acquire_release_on_exc(lock_type: type[BaseFileLock], tmp_path: Path) - with lock.acquire() as lock_1: assert lock is lock_1 assert lock.is_locked - raise Exception - except Exception: + raise ValueError + except ValueError: assert not lock.is_locked @@ -385,9 +385,8 @@ def test_del(lock_type: type[BaseFileLock], tmp_path: Path) -> None: def test_cleanup_soft_lock(tmp_path: Path) -> None: # tests if the lock file is removed after use lock_path = tmp_path / "a" - lock = SoftFileLock(str(lock_path)) - with lock: + with SoftFileLock(lock_path): assert lock_path.exists() assert not lock_path.exists() @@ -399,9 +398,9 @@ def test_poll_intervall_deprecated(lock_type: type[BaseFileLock], tmp_path: Path with pytest.deprecated_call(match="use poll_interval instead of poll_intervall") as checker: lock.acquire(poll_intervall=0.05) # the deprecation warning will be captured by the checker - frameinfo = getframeinfo(stack()[0][0]) # get frameinfo of current file and lineno (+1 than the above lineno) + frame_info = getframeinfo(stack()[0][0]) # get frame info of current file and lineno (+1 than the above lineno) for warning in checker: - if warning.filename == frameinfo.filename and warning.lineno + 1 == frameinfo.lineno: # pragma: no cover + if warning.filename == frame_info.filename and warning.lineno + 1 == frame_info.lineno: # pragma: no cover break else: # pragma: no cover pytest.fail("No warnings of stacklevel=2 matching.") @@ -490,7 +489,7 @@ def test_wrong_platform(tmp_path: Path) -> None: assert inspect.isabstract(BaseFileLock) lock_type = UnixFileLock if sys.platform == "win32" else WindowsFileLock - lock = lock_type(str(tmp_path / "lockfile")) + lock = lock_type(tmp_path / "lockfile") with pytest.raises(NotImplementedError): lock.acquire() @@ -502,5 +501,11 @@ def test_wrong_platform(tmp_path: Path) -> None: def test_flock_not_implemented_unix(tmp_path: Path, mocker: MockerFixture) -> None: mocker.patch("fcntl.flock", side_effect=OSError(ENOSYS, "mock error")) with pytest.raises(NotImplementedError): - with FileLock(str(tmp_path / "a.lock")): + with FileLock(tmp_path / "a.lock"): pass + + +def test_soft_errors(tmp_path: Path, mocker: MockerFixture) -> None: + mocker.patch("os.open", side_effect=OSError(ENOSYS, "mock error")) + with pytest.raises(OSError, match="mock error"): + SoftFileLock(tmp_path / "a.lock").acquire() diff --git a/whitelist.txt b/whitelist.txt index b55c5489..d5c4d2e4 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -7,7 +7,6 @@ enosys extlinks filelock filemode -frameinfo fspath getframeinfo intersphinx