diff options
author | Cserna Zsolt <cserna.zsolt@gmail.com> | 2020-10-28 08:27:43 +0100 |
---|---|---|
committer | Cserna Zsolt <cserna.zsolt@gmail.com> | 2020-10-31 17:40:56 +0100 |
commit | 8a38e7a6e8039de93c6f24935effd89f034d9c00 (patch) | |
tree | 3b49c5cf8a25e3f445a55b34344e081ac690144b /src/_pytest | |
parent | b95991aeeab28be6af94e24668414360e4d3a630 (diff) | |
download | pytest-8a38e7a6e8039de93c6f24935effd89f034d9c00.tar.gz |
Fix handling recursive symlinks
When pytest was run on a directory containing a recursive symlink it failed
with ELOOP as the library was not able to determine the type of the
direntry:
src/_pytest/main.py:685: in collect
if not direntry.is_file():
E OSError: [Errno 40] Too many levels of symbolic links: '/home/florian/proj/pytest/tests/recursive'
This is fixed by handling ELOOP and other errors in the visit function in
pathlib.py, so the entries whose is_file() call raises an OSError with the
pre-defined list of error numbers will be exluded from the result.
The _ignore_errors function was copied from Lib/pathlib.py of cpython 3.9.
Fixes #7951
Diffstat (limited to 'src/_pytest')
-rw-r--r-- | src/_pytest/pathlib.py | 39 |
1 files changed, 38 insertions, 1 deletions
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index f0bdb1481..a1c364076 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -9,6 +9,10 @@ import sys import uuid import warnings from enum import Enum +from errno import EBADF +from errno import ELOOP +from errno import ENOENT +from errno import ENOTDIR from functools import partial from os.path import expanduser from os.path import expandvars @@ -37,6 +41,24 @@ LOCK_TIMEOUT = 60 * 60 * 24 * 3 _AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath) +# The following function, variables and comments were +# copied from cpython 3.9 Lib/pathlib.py file. + +# EBADF - guard against macOS `stat` throwing EBADF +_IGNORED_ERRORS = (ENOENT, ENOTDIR, EBADF, ELOOP) + +_IGNORED_WINERRORS = ( + 21, # ERROR_NOT_READY - drive exists but is not accessible + 1921, # ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself +) + + +def _ignore_error(exception): + return ( + getattr(exception, "errno", None) in _IGNORED_ERRORS + or getattr(exception, "winerror", None) in _IGNORED_WINERRORS + ) + def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: return path.joinpath(".lock") @@ -555,8 +577,23 @@ def visit( Entries at each directory level are sorted. """ - entries = sorted(os.scandir(path), key=lambda entry: entry.name) + + # Skip entries with symlink loops and other brokenness, so the caller doesn't + # have to deal with it. + entries = [] + for entry in os.scandir(path): + try: + entry.is_file() + except OSError as err: + if _ignore_error(err): + continue + raise + entries.append(entry) + + entries.sort(key=lambda entry: entry.name) + yield from entries + for entry in entries: if entry.is_dir(follow_symlinks=False) and recurse(entry): yield from visit(entry.path, recurse) |