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

Reply via email to