diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b3129020d..c3235f8b8 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -569,3 +569,35 @@ def visit( for entry in entries: if entry.is_dir(follow_symlinks=False) and recurse(entry): yield from visit(entry.path, recurse) + + +def commonpath(path1: Path, path2: Path) -> Optional[Path]: + """Return the common part shared with the other path, or None if there is + no common part.""" + try: + return Path(os.path.commonpath((str(path1), str(path2)))) + except ValueError: + return None + + +def bestrelpath(directory: Path, dest: Path) -> str: + """Return a string which is a relative path from directory to dest such + that directory/bestrelpath == dest. + + If no such path can be determined, returns dest. + """ + if dest == directory: + return os.curdir + # Find the longest common directory. + base = commonpath(directory, dest) + # Can be the case on Windows. + if not base: + return str(dest) + reldirectory = directory.relative_to(base) + reldest = dest.relative_to(base) + return os.path.join( + # Back from directory to base. + *([os.pardir] * len(reldirectory.parts)), + # Forward from base to dest. + *reldest.parts, + ) diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 74dac21d9..41228d6b0 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -6,6 +6,8 @@ from textwrap import dedent import py import pytest +from _pytest.pathlib import bestrelpath +from _pytest.pathlib import commonpath from _pytest.pathlib import ensure_deletable from _pytest.pathlib import fnmatch_ex from _pytest.pathlib import get_extended_length_path_str @@ -381,3 +383,21 @@ def test_suppress_error_removing_lock(tmp_path): # check now that we can remove the lock file in normal circumstances assert ensure_deletable(path, consider_lock_dead_if_created_before=mtime + 30) assert not lock.is_file() + + +def test_bestrelpath() -> None: + curdir = Path("/foo/bar/baz/path") + assert bestrelpath(curdir, curdir) == "." + assert bestrelpath(curdir, curdir / "hello" / "world") == "hello" + os.sep + "world" + assert bestrelpath(curdir, curdir.parent / "sister") == ".." + os.sep + "sister" + assert bestrelpath(curdir, curdir.parent) == ".." + assert bestrelpath(curdir, Path("hello")) == "hello" + + +def test_commonpath() -> None: + path = Path("/foo/bar/baz/path") + subpath = path / "sampledir" + assert commonpath(path, subpath) == path + assert commonpath(subpath, path) == path + assert commonpath(Path(str(path) + "suffix"), path) == path.parent + assert commonpath(path, path.parent.parent) == path.parent.parent