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
This commit is contained in:
Cserna Zsolt
2020-10-28 08:27:43 +01:00
parent b95991aeea
commit 8a38e7a6e8
5 changed files with 67 additions and 1 deletions

View File

@@ -1404,3 +1404,17 @@ def test_does_not_crash_on_error_from_decorated_function(testdir: Testdir) -> No
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)."""
symlink_or_skip("recursive", testdir.tmpdir.join("recursive"))
testdir.makepyfile(
"""
def test_foo(): assert True
"""
)
result = testdir.runpytest()
assert result.ret == ExitCode.OK
assert result.parseoutcomes() == {"passed": 1}

View File

@@ -17,6 +17,8 @@ from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import maybe_delete_a_numbered_dir
from _pytest.pathlib import resolve_package_path
from _pytest.pathlib import symlink_or_skip
from _pytest.pathlib import visit
class TestFNMatcherPort:
@@ -401,3 +403,14 @@ def test_commonpath() -> None:
assert commonpath(subpath, path) == path
assert commonpath(Path(str(path) + "suffix"), path) == path.parent
assert commonpath(path, path.parent.parent) == path.parent.parent
def test_visit_ignores_errors(tmpdir) -> None:
symlink_or_skip("recursive", tmpdir.join("recursive"))
tmpdir.join("foo").write_binary(b"")
tmpdir.join("bar").write_binary(b"")
assert [entry.name for entry in visit(tmpdir, recurse=lambda entry: False)] == [
"bar",
"foo",
]