https://github.com/python/cpython/commit/cf9d1a4b6b28a76a49edba4028d5533195172287 commit: cf9d1a4b6b28a76a49edba4028d5533195172287 branch: main author: Barney Gale <barney.g...@gmail.com> committer: barneygale <barney.g...@gmail.com> date: 2025-03-21T22:18:20Z summary:
GH-128520: pathlib ABCs: allow tests to be run externally (#131315) Adjust the tests for the `pathlib.types` module so that they can be run against the `pathlib-abc` PyPI package, which is a backport of the module for older Python versions. Specifically, we add a `.support.is_pypi` switch that is false in the stdlib and true in the pathlib-abc package. This controls which package we import, and whether or not we run tests against `PurePath` and `Path`. For compatibility with older Python versions, we stop using `zipfile.ZipFile.mkdir()` and `zipfile.ZipInfo._for_archive()`. files: M Lib/test/test_pathlib/support/__init__.py M Lib/test/test_pathlib/support/lexical_path.py M Lib/test/test_pathlib/support/local_path.py M Lib/test/test_pathlib/support/zip_path.py M Lib/test/test_pathlib/test_copy.py M Lib/test/test_pathlib/test_join.py M Lib/test/test_pathlib/test_join_posix.py M Lib/test/test_pathlib/test_join_windows.py M Lib/test/test_pathlib/test_read.py M Lib/test/test_pathlib/test_write.py diff --git a/Lib/test/test_pathlib/support/__init__.py b/Lib/test/test_pathlib/support/__init__.py index e69de29bb2d1d6..dcaef654d77d68 100644 --- a/Lib/test/test_pathlib/support/__init__.py +++ b/Lib/test/test_pathlib/support/__init__.py @@ -0,0 +1,2 @@ +# Set to 'True' if the tests are run against the pathlib-abc PyPI package. +is_pypi = False diff --git a/Lib/test/test_pathlib/support/lexical_path.py b/Lib/test/test_pathlib/support/lexical_path.py index 6298513de6cf83..f29a521af9b013 100644 --- a/Lib/test/test_pathlib/support/lexical_path.py +++ b/Lib/test/test_pathlib/support/lexical_path.py @@ -4,11 +4,17 @@ import ntpath import os.path -import pathlib.types import posixpath +from . import is_pypi -class LexicalPath(pathlib.types._JoinablePath): +if is_pypi: + from pathlib_abc import _JoinablePath +else: + from pathlib.types import _JoinablePath + + +class LexicalPath(_JoinablePath): __slots__ = ('_segments',) parser = os.path diff --git a/Lib/test/test_pathlib/support/local_path.py b/Lib/test/test_pathlib/support/local_path.py index 1cf64316b40ca7..4f027754f6a6e1 100644 --- a/Lib/test/test_pathlib/support/local_path.py +++ b/Lib/test/test_pathlib/support/local_path.py @@ -7,25 +7,36 @@ """ import os -import pathlib.types -from test.support import os_helper -from test.test_pathlib.support.lexical_path import LexicalPath +from . import is_pypi +from .lexical_path import LexicalPath + +if is_pypi: + from shutil import rmtree + from pathlib_abc import PathInfo, _ReadablePath, _WritablePath + can_symlink = True + testfn = "TESTFN" +else: + from pathlib.types import PathInfo, _ReadablePath, _WritablePath + from test.support import os_helper + can_symlink = os_helper.can_symlink() + testfn = os_helper.TESTFN + rmtree = os_helper.rmtree class LocalPathGround: - can_symlink = os_helper.can_symlink() + can_symlink = can_symlink def __init__(self, path_cls): self.path_cls = path_cls def setup(self, local_suffix=""): - root = self.path_cls(os_helper.TESTFN + local_suffix) + root = self.path_cls(testfn + local_suffix) os.mkdir(root) return root def teardown(self, root): - os_helper.rmtree(root) + rmtree(root) def create_file(self, p, data=b''): with open(p, 'wb') as f: @@ -79,7 +90,7 @@ def readbytes(self, p): return f.read() -class LocalPathInfo(pathlib.types.PathInfo): +class LocalPathInfo(PathInfo): """ Simple implementation of PathInfo for a local path """ @@ -123,7 +134,7 @@ def is_symlink(self): return self._is_symlink -class ReadableLocalPath(pathlib.types._ReadablePath, LexicalPath): +class ReadableLocalPath(_ReadablePath, LexicalPath): """ Simple implementation of a ReadablePath class for local filesystem paths. """ @@ -146,7 +157,7 @@ def readlink(self): return self.with_segments(os.readlink(self)) -class WritableLocalPath(pathlib.types._WritablePath, LexicalPath): +class WritableLocalPath(_WritablePath, LexicalPath): """ Simple implementation of a WritablePath class for local filesystem paths. """ diff --git a/Lib/test/test_pathlib/support/zip_path.py b/Lib/test/test_pathlib/support/zip_path.py index 4e24e35a03afb7..242cab1509627b 100644 --- a/Lib/test/test_pathlib/support/zip_path.py +++ b/Lib/test/test_pathlib/support/zip_path.py @@ -8,12 +8,18 @@ import errno import io -import pathlib.types import posixpath import stat import zipfile from stat import S_IFMT, S_ISDIR, S_ISREG, S_ISLNK +from . import is_pypi + +if is_pypi: + from pathlib_abc import PathInfo, _ReadablePath, _WritablePath +else: + from pathlib.types import PathInfo, _ReadablePath, _WritablePath + class ZipPathGround: can_symlink = True @@ -31,7 +37,10 @@ def create_file(self, path, data=b''): path.zip_file.writestr(str(path), data) def create_dir(self, path): - path.zip_file.mkdir(str(path)) + zip_info = zipfile.ZipInfo(str(path) + '/') + zip_info.external_attr |= stat.S_IFDIR << 16 + zip_info.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY + path.zip_file.writestr(zip_info, '') def create_symlink(self, path, target): zip_info = zipfile.ZipInfo(str(path)) @@ -80,7 +89,7 @@ def islink(self, p): return stat.S_ISLNK(info.external_attr >> 16) -class MissingZipPathInfo: +class MissingZipPathInfo(PathInfo): """ PathInfo implementation that is used when a zip file member is missing. """ @@ -105,7 +114,7 @@ def resolve(self): missing_zip_path_info = MissingZipPathInfo() -class ZipPathInfo: +class ZipPathInfo(PathInfo): """ PathInfo implementation for an existing zip file member. """ @@ -216,7 +225,7 @@ def append(self, item): self.tree.resolve(item.filename, create=True).zip_info = item -class ReadableZipPath(pathlib.types._ReadablePath): +class ReadableZipPath(_ReadablePath): """ Simple implementation of a ReadablePath class for .zip files. """ @@ -279,7 +288,7 @@ def readlink(self): return self.with_segments(self.zip_file.read(info.zip_info).decode()) -class WritableZipPath(pathlib.types._WritablePath): +class WritableZipPath(_WritablePath): """ Simple implementation of a WritablePath class for .zip files. """ @@ -314,10 +323,13 @@ def __open_wb__(self, buffering=-1): return self.zip_file.open(str(self), 'w') def mkdir(self, mode=0o777): - self.zip_file.mkdir(str(self), mode) + zinfo = zipfile.ZipInfo(str(self) + '/') + zinfo.external_attr |= stat.S_IFDIR << 16 + zinfo.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY + self.zip_file.writestr(zinfo, '') def symlink_to(self, target, target_is_directory=False): - zinfo = zipfile.ZipInfo(str(self))._for_archive(self.zip_file) + zinfo = zipfile.ZipInfo(str(self)) zinfo.external_attr = stat.S_IFLNK << 16 if target_is_directory: zinfo.external_attr |= 0x10 diff --git a/Lib/test/test_pathlib/test_copy.py b/Lib/test/test_pathlib/test_copy.py index 698a0a7b75070f..5f4cf82a0314c4 100644 --- a/Lib/test/test_pathlib/test_copy.py +++ b/Lib/test/test_pathlib/test_copy.py @@ -5,10 +5,9 @@ import contextlib import unittest -from pathlib import Path - -from test.test_pathlib.support.local_path import LocalPathGround, WritableLocalPath -from test.test_pathlib.support.zip_path import ZipPathGround, ReadableZipPath, WritableZipPath +from .support import is_pypi +from .support.local_path import LocalPathGround +from .support.zip_path import ZipPathGround, ReadableZipPath, WritableZipPath class CopyTestBase: @@ -53,7 +52,7 @@ def test_copy_file_to_existing_file(self): self.target_ground.readbytes(result)) def test_copy_file_to_directory(self): - if not isinstance(self.target_root, WritableLocalPath): + if isinstance(self.target_root, WritableZipPath): self.skipTest('needs local target') source = self.source_root / 'fileA' target = self.target_root / 'copyA' @@ -113,7 +112,7 @@ def test_copy_dir_follow_symlinks_false(self): self.assertEqual(self.target_ground.readlink(target / 'linkD'), 'dirD') def test_copy_dir_to_existing_directory(self): - if not isinstance(self.target_root, WritableLocalPath): + if isinstance(self.target_root, WritableZipPath): self.skipTest('needs local target') source = self.source_root / 'dirC' target = self.target_root / 'copyC' @@ -153,19 +152,22 @@ class ZipToZipPathCopyTest(CopyTestBase, unittest.TestCase): target_ground = ZipPathGround(WritableZipPath) -class ZipToLocalPathCopyTest(CopyTestBase, unittest.TestCase): - source_ground = ZipPathGround(ReadableZipPath) - target_ground = LocalPathGround(Path) +if not is_pypi: + from pathlib import Path + class ZipToLocalPathCopyTest(CopyTestBase, unittest.TestCase): + source_ground = ZipPathGround(ReadableZipPath) + target_ground = LocalPathGround(Path) -class LocalToZipPathCopyTest(CopyTestBase, unittest.TestCase): - source_ground = LocalPathGround(Path) - target_ground = ZipPathGround(WritableZipPath) + + class LocalToZipPathCopyTest(CopyTestBase, unittest.TestCase): + source_ground = LocalPathGround(Path) + target_ground = ZipPathGround(WritableZipPath) -class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase): - source_ground = LocalPathGround(Path) - target_ground = LocalPathGround(Path) + class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase): + source_ground = LocalPathGround(Path) + target_ground = LocalPathGround(Path) if __name__ == "__main__": diff --git a/Lib/test/test_pathlib/test_join.py b/Lib/test/test_pathlib/test_join.py index 03a3ecfd248665..f1a24204b4c30a 100644 --- a/Lib/test/test_pathlib/test_join.py +++ b/Lib/test/test_pathlib/test_join.py @@ -4,9 +4,13 @@ import unittest -from pathlib import PurePath, Path -from pathlib.types import _PathParser, _JoinablePath -from test.test_pathlib.support.lexical_path import LexicalPath +from .support import is_pypi +from .support.lexical_path import LexicalPath + +if is_pypi: + from pathlib_abc import _PathParser, _JoinablePath +else: + from pathlib.types import _PathParser, _JoinablePath class JoinTestBase: @@ -355,12 +359,14 @@ class LexicalPathJoinTest(JoinTestBase, unittest.TestCase): cls = LexicalPath -class PurePathJoinTest(JoinTestBase, unittest.TestCase): - cls = PurePath +if not is_pypi: + from pathlib import PurePath, Path + class PurePathJoinTest(JoinTestBase, unittest.TestCase): + cls = PurePath -class PathJoinTest(JoinTestBase, unittest.TestCase): - cls = Path + class PathJoinTest(JoinTestBase, unittest.TestCase): + cls = Path if __name__ == "__main__": diff --git a/Lib/test/test_pathlib/test_join_posix.py b/Lib/test/test_pathlib/test_join_posix.py index 7f657c2565d5ec..d24fb1087c917c 100644 --- a/Lib/test/test_pathlib/test_join_posix.py +++ b/Lib/test/test_pathlib/test_join_posix.py @@ -5,8 +5,8 @@ import os import unittest -from pathlib import PurePosixPath, PosixPath -from test.test_pathlib.support.lexical_path import LexicalPosixPath +from .support import is_pypi +from .support.lexical_path import LexicalPosixPath class JoinTestBase: @@ -36,13 +36,15 @@ class LexicalPosixPathJoinTest(JoinTestBase, unittest.TestCase): cls = LexicalPosixPath -class PurePosixPathJoinTest(JoinTestBase, unittest.TestCase): - cls = PurePosixPath +if not is_pypi: + from pathlib import PurePosixPath, PosixPath + class PurePosixPathJoinTest(JoinTestBase, unittest.TestCase): + cls = PurePosixPath -if os.name != 'nt': - class PosixPathJoinTest(JoinTestBase, unittest.TestCase): - cls = PosixPath + if os.name != 'nt': + class PosixPathJoinTest(JoinTestBase, unittest.TestCase): + cls = PosixPath if __name__ == "__main__": diff --git a/Lib/test/test_pathlib/test_join_windows.py b/Lib/test/test_pathlib/test_join_windows.py index 783248d1697eef..2cc634f25efc68 100644 --- a/Lib/test/test_pathlib/test_join_windows.py +++ b/Lib/test/test_pathlib/test_join_windows.py @@ -5,8 +5,8 @@ import os import unittest -from pathlib import PureWindowsPath, WindowsPath -from test.test_pathlib.support.lexical_path import LexicalWindowsPath +from .support import is_pypi +from .support.lexical_path import LexicalWindowsPath class JoinTestBase: @@ -40,8 +40,6 @@ def test_join(self): pp = p.joinpath('E:d:s') self.assertEqual(pp, P('E:d:s')) # Joining onto a UNC path with no root - pp = P('//').joinpath('server') - self.assertEqual(pp, P('//server')) pp = P('//server').joinpath('share') self.assertEqual(pp, P(r'//server\share')) pp = P('//./BootPartition').joinpath('Windows') @@ -54,7 +52,7 @@ def test_div(self): self.assertEqual(p / 'x/y', P(r'C:/a/b\x/y')) self.assertEqual(p / 'x' / 'y', P(r'C:/a/b\x\y')) self.assertEqual(p / '/x/y', P('C:/x/y')) - self.assertEqual(p / '/x' / 'y', P('C:/x\y')) + self.assertEqual(p / '/x' / 'y', P(r'C:/x\y')) # Joining with a different drive => the first path is ignored, even # if the second path is relative. self.assertEqual(p / 'D:x/y', P('D:x/y')) @@ -277,13 +275,15 @@ class LexicalWindowsPathJoinTest(JoinTestBase, unittest.TestCase): cls = LexicalWindowsPath -class PureWindowsPathJoinTest(JoinTestBase, unittest.TestCase): - cls = PureWindowsPath +if not is_pypi: + from pathlib import PureWindowsPath, WindowsPath + class PureWindowsPathJoinTest(JoinTestBase, unittest.TestCase): + cls = PureWindowsPath -if os.name == 'nt': - class WindowsPathJoinTest(JoinTestBase, unittest.TestCase): - cls = WindowsPath + if os.name == 'nt': + class WindowsPathJoinTest(JoinTestBase, unittest.TestCase): + cls = WindowsPath if __name__ == "__main__": diff --git a/Lib/test/test_pathlib/test_read.py b/Lib/test/test_pathlib/test_read.py index 49015dac3b3998..938d3a1e987128 100644 --- a/Lib/test/test_pathlib/test_read.py +++ b/Lib/test/test_pathlib/test_read.py @@ -6,12 +6,16 @@ import io import unittest -from pathlib import Path -from pathlib.types import PathInfo, _ReadablePath -from pathlib._os import magic_open +from .support import is_pypi +from .support.local_path import ReadableLocalPath, LocalPathGround +from .support.zip_path import ReadableZipPath, ZipPathGround -from test.test_pathlib.support.local_path import ReadableLocalPath, LocalPathGround -from test.test_pathlib.support.zip_path import ReadableZipPath, ZipPathGround +if is_pypi: + from pathlib_abc import PathInfo, _ReadablePath + from pathlib_abc._os import magic_open +else: + from pathlib.types import PathInfo, _ReadablePath + from pathlib._os import magic_open class ReadTestBase: @@ -301,8 +305,11 @@ class LocalPathReadTest(ReadTestBase, unittest.TestCase): ground = LocalPathGround(ReadableLocalPath) -class PathReadTest(ReadTestBase, unittest.TestCase): - ground = LocalPathGround(Path) +if not is_pypi: + from pathlib import Path + + class PathReadTest(ReadTestBase, unittest.TestCase): + ground = LocalPathGround(Path) if __name__ == "__main__": diff --git a/Lib/test/test_pathlib/test_write.py b/Lib/test/test_pathlib/test_write.py index 3d6057fb9dd4a4..040af7be152fc2 100644 --- a/Lib/test/test_pathlib/test_write.py +++ b/Lib/test/test_pathlib/test_write.py @@ -6,12 +6,16 @@ import os import unittest -from pathlib import Path -from pathlib.types import _WritablePath -from pathlib._os import magic_open +from .support import is_pypi +from .support.local_path import WritableLocalPath, LocalPathGround +from .support.zip_path import WritableZipPath, ZipPathGround -from test.test_pathlib.support.local_path import WritableLocalPath, LocalPathGround -from test.test_pathlib.support.zip_path import WritableZipPath, ZipPathGround +if is_pypi: + from pathlib_abc import _WritablePath + from pathlib_abc._os import magic_open +else: + from pathlib.types import _WritablePath + from pathlib._os import magic_open class WriteTestBase: @@ -101,8 +105,11 @@ class LocalPathWriteTest(WriteTestBase, unittest.TestCase): ground = LocalPathGround(WritableLocalPath) -class PathWriteTest(WriteTestBase, unittest.TestCase): - ground = LocalPathGround(Path) +if not is_pypi: + from pathlib import Path + + class PathWriteTest(WriteTestBase, unittest.TestCase): + ground = LocalPathGround(Path) if __name__ == "__main__": _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com