Skip to content

Commit

Permalink
Fix handling recursive symlinks
Browse files Browse the repository at this point in the history
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 now by handling ELOOP on Linux and ERROR_CANT_RESOLVE_FILENAME
error on windows.

Fixes pytest-dev#7951
  • Loading branch information
csernazs committed Oct 29, 2020
1 parent b95991a commit 4abf898
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog/7951.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed handling of recursive symlinks when collecting tests.
15 changes: 13 additions & 2 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Core implementation of the testing process: init, session, runtest loop."""
import argparse
import errno
import fnmatch
import functools
import importlib
Expand Down Expand Up @@ -676,8 +677,18 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:

seen_dirs: Set[py.path.local] = set()
for direntry in visit(str(argpath), self._recurse):
if not direntry.is_file():
continue
try:
if not direntry.is_file():
continue
except OSError as err:
# 1912: ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
if (
getattr(err, "errno", None) == errno.ELOOP
or getattr(err, "winerror", None) == 1912
):
continue
else:
raise

path = py.path.local(direntry.path)
dirpath = path.dirpath()
Expand Down
14 changes: 14 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1404,3 +1404,17 @@ def a(): return 4
result = testdir.runpytest()
# Not INTERNAL_ERROR
assert result.ret == ExitCode.INTERRUPTED


def test_does_not_crash_on_recursive_symlink(testdir: Testdir) -> None:
"""Regression test for an issue around recursive symlinks (#7951)."""
testdir.makepyfile(
"""
def test_foo(): assert True
"""
)
os.symlink("recursive", str(testdir.tmpdir.join("recursive")))
result = testdir.runpytest()

assert result.ret == ExitCode.OK
assert result.parseoutcomes() == {"passed": 1}

0 comments on commit 4abf898

Please sign in to comment.