summaryrefslogtreecommitdiff
path: root/src/_pytest
diff options
context:
space:
mode:
authorCserna Zsolt <cserna.zsolt@gmail.com>2020-10-28 08:27:43 +0100
committerCserna Zsolt <cserna.zsolt@gmail.com>2020-10-31 17:40:56 +0100
commit8a38e7a6e8039de93c6f24935effd89f034d9c00 (patch)
tree3b49c5cf8a25e3f445a55b34344e081ac690144b /src/_pytest
parentb95991aeeab28be6af94e24668414360e4d3a630 (diff)
downloadpytest-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.py39
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)