diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index d3908a3fd..63764b341 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -583,7 +583,7 @@ def resolve_package_path(path: Path) -> Optional[Path]: def visit( - path: str, recurse: Callable[["os.DirEntry[str]"], bool] + path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] ) -> Iterator["os.DirEntry[str]"]: """Walk a directory recursively, in breadth-first order. @@ -657,3 +657,21 @@ def bestrelpath(directory: Path, dest: Path) -> str: # Forward from base to dest. *reldest.parts, ) + + +# Originates from py. path.local.copy(), with siginficant trims and adjustments. +# TODO(py38): Replace with shutil.copytree(..., symlinks=True, dirs_exist_ok=True) +def copytree(source: Path, target: Path) -> None: + """Recursively copy a source directory to target.""" + assert source.is_dir() + for entry in visit(source, recurse=lambda entry: not entry.is_symlink()): + x = Path(entry) + relpath = x.relative_to(source) + newx = target / relpath + newx.parent.mkdir(exist_ok=True) + if x.is_symlink(): + newx.symlink_to(os.readlink(x)) + elif x.is_file(): + shutil.copyfile(x, newx) + elif x.is_dir(): + newx.mkdir(exist_ok=True) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index febae0785..968a53651 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -63,6 +63,7 @@ from _pytest.outcomes import fail from _pytest.outcomes import importorskip from _pytest.outcomes import skip from _pytest.pathlib import bestrelpath +from _pytest.pathlib import copytree from _pytest.pathlib import make_numbered_dir from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -935,10 +936,7 @@ class Pytester: example_path = example_dir.joinpath(name) if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file(): - # TODO: legacy_path.copy can copy files to existing directories, - # while with shutil.copytree the destination directory cannot exist, - # we will need to roll our own in order to drop legacy_path completely - legacy_path(example_path).copy(legacy_path(self.path)) + copytree(example_path, self.path) return self.path elif example_path.is_file(): result = self.path.joinpath(example_path.name)