Skip to content

Commit d3db71f

Browse files
committed
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 now by handling ELOOP on Linux and ERROR_CANT_RESOLVE_FILENAME error on windows. Fixes #7951
1 parent b95991a commit d3db71f

File tree

3 files changed

+28
-2
lines changed

3 files changed

+28
-2
lines changed

changelog/7951.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed handling of recursive symlinks when collecting tests.

src/_pytest/main.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Core implementation of the testing process: init, session, runtest loop."""
22
import argparse
3+
import errno
34
import fnmatch
45
import functools
56
import importlib
@@ -676,8 +677,18 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
676677

677678
seen_dirs: Set[py.path.local] = set()
678679
for direntry in visit(str(argpath), self._recurse):
679-
if not direntry.is_file():
680-
continue
680+
try:
681+
if not direntry.is_file():
682+
continue
683+
except OSError as err:
684+
# 1921: ERROR_CANT_RESOLVE_FILENAME - fix for broken symlink pointing to itself
685+
if (
686+
getattr(err, "errno", None) == errno.ELOOP
687+
or getattr(err, "winerror", None) == 1921
688+
):
689+
continue
690+
else:
691+
raise
681692

682693
path = py.path.local(direntry.path)
683694
dirpath = path.dirpath()

testing/test_collection.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,3 +1404,17 @@ def a(): return 4
14041404
result = testdir.runpytest()
14051405
# Not INTERNAL_ERROR
14061406
assert result.ret == ExitCode.INTERRUPTED
1407+
1408+
1409+
def test_does_not_crash_on_recursive_symlink(testdir: Testdir) -> None:
1410+
"""Regression test for an issue around recursive symlinks (#7951)."""
1411+
testdir.makepyfile(
1412+
"""
1413+
def test_foo(): assert True
1414+
"""
1415+
)
1416+
os.symlink("recursive", str(testdir.tmpdir.join("recursive")))
1417+
result = testdir.runpytest()
1418+
1419+
assert result.ret == ExitCode.OK
1420+
assert result.parseoutcomes() == {"passed": 1}

0 commit comments

Comments
 (0)