Skip to content

Commit

Permalink
Merge pull request #7541 from bluetech/py-visit
Browse files Browse the repository at this point in the history
pathlib: stop using py.path.local.visit(), use os.scandir
  • Loading branch information
bluetech committed Jul 29, 2020
2 parents 20a3a28 + 3633b69 commit b473e51
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 28 deletions.
15 changes: 7 additions & 8 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit
from _pytest.pathlib import Path
from _pytest.pathlib import visit
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
from _pytest.runner import collect_one_node
Expand Down Expand Up @@ -617,10 +618,13 @@ def _collect(
assert not names, "invalid arg {!r}".format((argpath, names))

seen_dirs = set() # type: Set[py.path.local]
for path in argpath.visit(
fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
):
for direntry in visit(str(argpath), self._recurse):
if not direntry.is_file():
continue

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

if dirpath not in seen_dirs:
# Collect packages first.
seen_dirs.add(dirpath)
Expand Down Expand Up @@ -668,11 +672,6 @@ def _collect(
return
yield from m

@staticmethod
def _visit_filter(f: py.path.local) -> bool:
# TODO: Remove type: ignore once `py` is typed.
return f.check(file=1) # type: ignore

def _tryconvertpyarg(self, x: str) -> str:
"""Convert a dotted module name to path."""
try:
Expand Down
15 changes: 8 additions & 7 deletions src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,17 +562,18 @@ def _gethookproxy(self, fspath: py.path.local):
def gethookproxy(self, fspath: py.path.local):
raise NotImplementedError()

def _recurse(self, dirpath: py.path.local) -> bool:
if dirpath.basename == "__pycache__":
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
if direntry.name == "__pycache__":
return False
ihook = self._gethookproxy(dirpath.dirpath())
if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
path = py.path.local(direntry.path)
ihook = self._gethookproxy(path.dirpath())
if ihook.pytest_ignore_collect(path=path, config=self.config):
return False
for pat in self._norecursepatterns:
if dirpath.check(fnmatch=pat):
if path.check(fnmatch=pat):
return False
ihook = self._gethookproxy(dirpath)
ihook.pytest_collect_directory(path=dirpath, parent=self)
ihook = self._gethookproxy(path)
ihook.pytest_collect_directory(path=path, parent=self)
return True

def isinitpath(self, path: py.path.local) -> bool:
Expand Down
15 changes: 15 additions & 0 deletions src/_pytest/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from os.path import sep
from posixpath import sep as posix_sep
from types import ModuleType
from typing import Callable
from typing import Iterable
from typing import Iterator
from typing import Optional
Expand Down Expand Up @@ -556,3 +557,17 @@ def resolve_package_path(path: Path) -> Optional[Path]:
break
result = parent
return result


def visit(
path: str, recurse: Callable[["os.DirEntry[str]"], bool]
) -> Iterator["os.DirEntry[str]"]:
"""Walk a directory recursively, in breadth-first order.
Entries at each directory level are sorted.
"""
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)
16 changes: 9 additions & 7 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError
from _pytest.pathlib import parts
from _pytest.pathlib import visit
from _pytest.reports import TerminalRepr
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
Expand Down Expand Up @@ -641,23 +642,24 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
):
yield Module.from_parent(self, fspath=init_module)
pkg_prefixes = set() # type: Set[py.path.local]
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
for direntry in visit(str(this_path), recurse=self._recurse):
path = py.path.local(direntry.path)

# We will visit our own __init__.py file, in which case we skip it.
is_file = path.isfile()
if is_file:
if path.basename == "__init__.py" and path.dirpath() == this_path:
if direntry.is_file():
if direntry.name == "__init__.py" and path.dirpath() == this_path:
continue

parts_ = parts(path.strpath)
parts_ = parts(direntry.path)
if any(
str(pkg_prefix) in parts_ and pkg_prefix.join("__init__.py") != path
for pkg_prefix in pkg_prefixes
):
continue

if is_file:
if direntry.is_file():
yield from self._collectfile(path)
elif not path.isdir():
elif not direntry.is_dir():
# Broken symlink or invalid/missing file.
continue
elif path.join("__init__.py").check(file=1):
Expand Down
4 changes: 2 additions & 2 deletions testing/python/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ def test_extend_fixture_conftest_module(self, testdir):
p = testdir.copy_example()
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest(next(p.visit("test_*.py")))
result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py"))))
result.stdout.fnmatch_lines(["*1 passed*"])

def test_extend_fixture_conftest_conftest(self, testdir):
p = testdir.copy_example()
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest(next(p.visit("test_*.py")))
result = testdir.runpytest(str(next(Path(str(p)).rglob("test_*.py"))))
result.stdout.fnmatch_lines(["*1 passed*"])

def test_extend_fixture_conftest_plugin(self, testdir):
Expand Down
5 changes: 3 additions & 2 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from _pytest.config import ExitCode
from _pytest.main import _in_venv
from _pytest.main import Session
from _pytest.pathlib import Path
from _pytest.pathlib import symlink_or_skip
from _pytest.pytester import Testdir

Expand Down Expand Up @@ -115,8 +116,8 @@ def test_ignored_certain_directories(self, testdir):
tmpdir.ensure(".whatever", "test_notfound.py")
tmpdir.ensure(".bzr", "test_notfound.py")
tmpdir.ensure("normal", "test_found.py")
for x in tmpdir.visit("test_*.py"):
x.write("def test_hello(): pass")
for x in Path(str(tmpdir)).rglob("test_*.py"):
x.write_text("def test_hello(): pass", "utf-8")

result = testdir.runpytest("--collect-only")
s = result.stdout.str()
Expand Down
5 changes: 3 additions & 2 deletions testing/test_conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,9 @@ def test_no_conftest(fxtr):
)
)
print("created directory structure:")
for x in testdir.tmpdir.visit():
print(" " + x.relto(testdir.tmpdir))
tmppath = Path(str(testdir.tmpdir))
for x in tmppath.rglob(""):
print(" " + str(x.relative_to(tmppath)))

return {"runner": runner, "package": package, "swc": swc, "snc": snc}

Expand Down

0 comments on commit b473e51

Please sign in to comment.