Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import atexit
import contextlib
import errno
import fnmatch
import importlib.util
import itertools
Expand Down Expand Up @@ -549,17 +550,40 @@ def resolve_package_path(path: Path) -> Optional[Path]:


def visit(
path: str, recurse: Callable[["os.DirEntry[str]"], bool]
path: str,
recurse: Callable[["os.DirEntry[str]"], bool],
*,
_seen: Optional[Set[str]] = None,
) -> Iterator["os.DirEntry[str]"]:
"""Walk a directory recursively, in breadth-first order.

Entries at each directory level are sorted.
"""
if _seen is None:
_seen = set()

real_path = os.path.realpath(path)
if real_path in _seen:
return
_seen.add(real_path)

entries = sorted(os.scandir(path), 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)
try:
is_directory = entry.is_dir(follow_symlinks=True)
except OSError as exc:
if exc.errno in (
errno.ENOENT,
errno.ENOTDIR,
errno.ELOOP,
errno.EACCES,
errno.EPERM,
):
continue
raise
if is_directory and recurse(entry):
yield from visit(entry.path, recurse, _seen=_seen)


def absolutepath(path: Union[Path, str]) -> Path:
Expand Down
26 changes: 26 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1256,6 +1256,32 @@ def test_collect_sub_with_symlinks(use_pkg, testdir):
)


def test_collect_symlinked_directory_argument(testdir):
target = testdir.mkdir("real_dir")
target.join("test_linked.py").write("def test_linked(): pass")

link = testdir.tmpdir.join("link_dir")
symlink_or_skip(target, link, target_is_directory=True)

result = testdir.runpytest("-v", str(link))
result.stdout.fnmatch_lines(
("link_dir/test_linked.py::test_linked PASSED*", "*1 passed in*",)
)


def test_collect_symlinked_subdirectory(testdir):
target = testdir.mkdir("real_pkg")
target.join("test_inside.py").write("def test_inside(): pass")

root = testdir.mkdir("root")
symlink_or_skip(target, root.join("link_pkg"), target_is_directory=True)

result = testdir.runpytest("-v", str(root))
result.stdout.fnmatch_lines(
("root/link_pkg/test_inside.py::test_inside PASSED*", "*1 passed in*",)
)


def test_collector_respects_tbstyle(testdir):
p1 = testdir.makepyfile("assert 0")
result = testdir.runpytest(p1, "--tb=native")
Expand Down