Skip to content

Commit cf9d1a4

Browse files
authored
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()`.
1 parent 56d0f9a commit cf9d1a4

10 files changed

+127
-72
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Set to 'True' if the tests are run against the pathlib-abc PyPI package.
2+
is_pypi = False

Lib/test/test_pathlib/support/lexical_path.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44

55
import ntpath
66
import os.path
7-
import pathlib.types
87
import posixpath
98

9+
from . import is_pypi
1010

11-
class LexicalPath(pathlib.types._JoinablePath):
11+
if is_pypi:
12+
from pathlib_abc import _JoinablePath
13+
else:
14+
from pathlib.types import _JoinablePath
15+
16+
17+
class LexicalPath(_JoinablePath):
1218
__slots__ = ('_segments',)
1319
parser = os.path
1420

Lib/test/test_pathlib/support/local_path.py

+20-9
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,36 @@
77
"""
88

99
import os
10-
import pathlib.types
1110

12-
from test.support import os_helper
13-
from test.test_pathlib.support.lexical_path import LexicalPath
11+
from . import is_pypi
12+
from .lexical_path import LexicalPath
13+
14+
if is_pypi:
15+
from shutil import rmtree
16+
from pathlib_abc import PathInfo, _ReadablePath, _WritablePath
17+
can_symlink = True
18+
testfn = "TESTFN"
19+
else:
20+
from pathlib.types import PathInfo, _ReadablePath, _WritablePath
21+
from test.support import os_helper
22+
can_symlink = os_helper.can_symlink()
23+
testfn = os_helper.TESTFN
24+
rmtree = os_helper.rmtree
1425

1526

1627
class LocalPathGround:
17-
can_symlink = os_helper.can_symlink()
28+
can_symlink = can_symlink
1829

1930
def __init__(self, path_cls):
2031
self.path_cls = path_cls
2132

2233
def setup(self, local_suffix=""):
23-
root = self.path_cls(os_helper.TESTFN + local_suffix)
34+
root = self.path_cls(testfn + local_suffix)
2435
os.mkdir(root)
2536
return root
2637

2738
def teardown(self, root):
28-
os_helper.rmtree(root)
39+
rmtree(root)
2940

3041
def create_file(self, p, data=b''):
3142
with open(p, 'wb') as f:
@@ -79,7 +90,7 @@ def readbytes(self, p):
7990
return f.read()
8091

8192

82-
class LocalPathInfo(pathlib.types.PathInfo):
93+
class LocalPathInfo(PathInfo):
8394
"""
8495
Simple implementation of PathInfo for a local path
8596
"""
@@ -123,7 +134,7 @@ def is_symlink(self):
123134
return self._is_symlink
124135

125136

126-
class ReadableLocalPath(pathlib.types._ReadablePath, LexicalPath):
137+
class ReadableLocalPath(_ReadablePath, LexicalPath):
127138
"""
128139
Simple implementation of a ReadablePath class for local filesystem paths.
129140
"""
@@ -146,7 +157,7 @@ def readlink(self):
146157
return self.with_segments(os.readlink(self))
147158

148159

149-
class WritableLocalPath(pathlib.types._WritablePath, LexicalPath):
160+
class WritableLocalPath(_WritablePath, LexicalPath):
150161
"""
151162
Simple implementation of a WritablePath class for local filesystem paths.
152163
"""

Lib/test/test_pathlib/support/zip_path.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88

99
import errno
1010
import io
11-
import pathlib.types
1211
import posixpath
1312
import stat
1413
import zipfile
1514
from stat import S_IFMT, S_ISDIR, S_ISREG, S_ISLNK
1615

16+
from . import is_pypi
17+
18+
if is_pypi:
19+
from pathlib_abc import PathInfo, _ReadablePath, _WritablePath
20+
else:
21+
from pathlib.types import PathInfo, _ReadablePath, _WritablePath
22+
1723

1824
class ZipPathGround:
1925
can_symlink = True
@@ -31,7 +37,10 @@ def create_file(self, path, data=b''):
3137
path.zip_file.writestr(str(path), data)
3238

3339
def create_dir(self, path):
34-
path.zip_file.mkdir(str(path))
40+
zip_info = zipfile.ZipInfo(str(path) + '/')
41+
zip_info.external_attr |= stat.S_IFDIR << 16
42+
zip_info.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
43+
path.zip_file.writestr(zip_info, '')
3544

3645
def create_symlink(self, path, target):
3746
zip_info = zipfile.ZipInfo(str(path))
@@ -80,7 +89,7 @@ def islink(self, p):
8089
return stat.S_ISLNK(info.external_attr >> 16)
8190

8291

83-
class MissingZipPathInfo:
92+
class MissingZipPathInfo(PathInfo):
8493
"""
8594
PathInfo implementation that is used when a zip file member is missing.
8695
"""
@@ -105,7 +114,7 @@ def resolve(self):
105114
missing_zip_path_info = MissingZipPathInfo()
106115

107116

108-
class ZipPathInfo:
117+
class ZipPathInfo(PathInfo):
109118
"""
110119
PathInfo implementation for an existing zip file member.
111120
"""
@@ -216,7 +225,7 @@ def append(self, item):
216225
self.tree.resolve(item.filename, create=True).zip_info = item
217226

218227

219-
class ReadableZipPath(pathlib.types._ReadablePath):
228+
class ReadableZipPath(_ReadablePath):
220229
"""
221230
Simple implementation of a ReadablePath class for .zip files.
222231
"""
@@ -279,7 +288,7 @@ def readlink(self):
279288
return self.with_segments(self.zip_file.read(info.zip_info).decode())
280289

281290

282-
class WritableZipPath(pathlib.types._WritablePath):
291+
class WritableZipPath(_WritablePath):
283292
"""
284293
Simple implementation of a WritablePath class for .zip files.
285294
"""
@@ -314,10 +323,13 @@ def __open_wb__(self, buffering=-1):
314323
return self.zip_file.open(str(self), 'w')
315324

316325
def mkdir(self, mode=0o777):
317-
self.zip_file.mkdir(str(self), mode)
326+
zinfo = zipfile.ZipInfo(str(self) + '/')
327+
zinfo.external_attr |= stat.S_IFDIR << 16
328+
zinfo.external_attr |= stat.FILE_ATTRIBUTE_DIRECTORY
329+
self.zip_file.writestr(zinfo, '')
318330

319331
def symlink_to(self, target, target_is_directory=False):
320-
zinfo = zipfile.ZipInfo(str(self))._for_archive(self.zip_file)
332+
zinfo = zipfile.ZipInfo(str(self))
321333
zinfo.external_attr = stat.S_IFLNK << 16
322334
if target_is_directory:
323335
zinfo.external_attr |= 0x10

Lib/test/test_pathlib/test_copy.py

+17-15
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
import contextlib
66
import unittest
77

8-
from pathlib import Path
9-
10-
from test.test_pathlib.support.local_path import LocalPathGround, WritableLocalPath
11-
from test.test_pathlib.support.zip_path import ZipPathGround, ReadableZipPath, WritableZipPath
8+
from .support import is_pypi
9+
from .support.local_path import LocalPathGround
10+
from .support.zip_path import ZipPathGround, ReadableZipPath, WritableZipPath
1211

1312

1413
class CopyTestBase:
@@ -53,7 +52,7 @@ def test_copy_file_to_existing_file(self):
5352
self.target_ground.readbytes(result))
5453

5554
def test_copy_file_to_directory(self):
56-
if not isinstance(self.target_root, WritableLocalPath):
55+
if isinstance(self.target_root, WritableZipPath):
5756
self.skipTest('needs local target')
5857
source = self.source_root / 'fileA'
5958
target = self.target_root / 'copyA'
@@ -113,7 +112,7 @@ def test_copy_dir_follow_symlinks_false(self):
113112
self.assertEqual(self.target_ground.readlink(target / 'linkD'), 'dirD')
114113

115114
def test_copy_dir_to_existing_directory(self):
116-
if not isinstance(self.target_root, WritableLocalPath):
115+
if isinstance(self.target_root, WritableZipPath):
117116
self.skipTest('needs local target')
118117
source = self.source_root / 'dirC'
119118
target = self.target_root / 'copyC'
@@ -153,19 +152,22 @@ class ZipToZipPathCopyTest(CopyTestBase, unittest.TestCase):
153152
target_ground = ZipPathGround(WritableZipPath)
154153

155154

156-
class ZipToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
157-
source_ground = ZipPathGround(ReadableZipPath)
158-
target_ground = LocalPathGround(Path)
155+
if not is_pypi:
156+
from pathlib import Path
159157

158+
class ZipToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
159+
source_ground = ZipPathGround(ReadableZipPath)
160+
target_ground = LocalPathGround(Path)
160161

161-
class LocalToZipPathCopyTest(CopyTestBase, unittest.TestCase):
162-
source_ground = LocalPathGround(Path)
163-
target_ground = ZipPathGround(WritableZipPath)
162+
163+
class LocalToZipPathCopyTest(CopyTestBase, unittest.TestCase):
164+
source_ground = LocalPathGround(Path)
165+
target_ground = ZipPathGround(WritableZipPath)
164166

165167

166-
class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
167-
source_ground = LocalPathGround(Path)
168-
target_ground = LocalPathGround(Path)
168+
class LocalToLocalPathCopyTest(CopyTestBase, unittest.TestCase):
169+
source_ground = LocalPathGround(Path)
170+
target_ground = LocalPathGround(Path)
169171

170172

171173
if __name__ == "__main__":

Lib/test/test_pathlib/test_join.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44

55
import unittest
66

7-
from pathlib import PurePath, Path
8-
from pathlib.types import _PathParser, _JoinablePath
9-
from test.test_pathlib.support.lexical_path import LexicalPath
7+
from .support import is_pypi
8+
from .support.lexical_path import LexicalPath
9+
10+
if is_pypi:
11+
from pathlib_abc import _PathParser, _JoinablePath
12+
else:
13+
from pathlib.types import _PathParser, _JoinablePath
1014

1115

1216
class JoinTestBase:
@@ -355,12 +359,14 @@ class LexicalPathJoinTest(JoinTestBase, unittest.TestCase):
355359
cls = LexicalPath
356360

357361

358-
class PurePathJoinTest(JoinTestBase, unittest.TestCase):
359-
cls = PurePath
362+
if not is_pypi:
363+
from pathlib import PurePath, Path
360364

365+
class PurePathJoinTest(JoinTestBase, unittest.TestCase):
366+
cls = PurePath
361367

362-
class PathJoinTest(JoinTestBase, unittest.TestCase):
363-
cls = Path
368+
class PathJoinTest(JoinTestBase, unittest.TestCase):
369+
cls = Path
364370

365371

366372
if __name__ == "__main__":

Lib/test/test_pathlib/test_join_posix.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import os
66
import unittest
77

8-
from pathlib import PurePosixPath, PosixPath
9-
from test.test_pathlib.support.lexical_path import LexicalPosixPath
8+
from .support import is_pypi
9+
from .support.lexical_path import LexicalPosixPath
1010

1111

1212
class JoinTestBase:
@@ -36,13 +36,15 @@ class LexicalPosixPathJoinTest(JoinTestBase, unittest.TestCase):
3636
cls = LexicalPosixPath
3737

3838

39-
class PurePosixPathJoinTest(JoinTestBase, unittest.TestCase):
40-
cls = PurePosixPath
39+
if not is_pypi:
40+
from pathlib import PurePosixPath, PosixPath
4141

42+
class PurePosixPathJoinTest(JoinTestBase, unittest.TestCase):
43+
cls = PurePosixPath
4244

43-
if os.name != 'nt':
44-
class PosixPathJoinTest(JoinTestBase, unittest.TestCase):
45-
cls = PosixPath
45+
if os.name != 'nt':
46+
class PosixPathJoinTest(JoinTestBase, unittest.TestCase):
47+
cls = PosixPath
4648

4749

4850
if __name__ == "__main__":

Lib/test/test_pathlib/test_join_windows.py

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
import os
66
import unittest
77

8-
from pathlib import PureWindowsPath, WindowsPath
9-
from test.test_pathlib.support.lexical_path import LexicalWindowsPath
8+
from .support import is_pypi
9+
from .support.lexical_path import LexicalWindowsPath
1010

1111

1212
class JoinTestBase:
@@ -40,8 +40,6 @@ def test_join(self):
4040
pp = p.joinpath('E:d:s')
4141
self.assertEqual(pp, P('E:d:s'))
4242
# Joining onto a UNC path with no root
43-
pp = P('//').joinpath('server')
44-
self.assertEqual(pp, P('//server'))
4543
pp = P('//server').joinpath('share')
4644
self.assertEqual(pp, P(r'//server\share'))
4745
pp = P('//./BootPartition').joinpath('Windows')
@@ -54,7 +52,7 @@ def test_div(self):
5452
self.assertEqual(p / 'x/y', P(r'C:/a/b\x/y'))
5553
self.assertEqual(p / 'x' / 'y', P(r'C:/a/b\x\y'))
5654
self.assertEqual(p / '/x/y', P('C:/x/y'))
57-
self.assertEqual(p / '/x' / 'y', P('C:/x\y'))
55+
self.assertEqual(p / '/x' / 'y', P(r'C:/x\y'))
5856
# Joining with a different drive => the first path is ignored, even
5957
# if the second path is relative.
6058
self.assertEqual(p / 'D:x/y', P('D:x/y'))
@@ -277,13 +275,15 @@ class LexicalWindowsPathJoinTest(JoinTestBase, unittest.TestCase):
277275
cls = LexicalWindowsPath
278276

279277

280-
class PureWindowsPathJoinTest(JoinTestBase, unittest.TestCase):
281-
cls = PureWindowsPath
278+
if not is_pypi:
279+
from pathlib import PureWindowsPath, WindowsPath
282280

281+
class PureWindowsPathJoinTest(JoinTestBase, unittest.TestCase):
282+
cls = PureWindowsPath
283283

284-
if os.name == 'nt':
285-
class WindowsPathJoinTest(JoinTestBase, unittest.TestCase):
286-
cls = WindowsPath
284+
if os.name == 'nt':
285+
class WindowsPathJoinTest(JoinTestBase, unittest.TestCase):
286+
cls = WindowsPath
287287

288288

289289
if __name__ == "__main__":

Lib/test/test_pathlib/test_read.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
import io
77
import unittest
88

9-
from pathlib import Path
10-
from pathlib.types import PathInfo, _ReadablePath
11-
from pathlib._os import magic_open
9+
from .support import is_pypi
10+
from .support.local_path import ReadableLocalPath, LocalPathGround
11+
from .support.zip_path import ReadableZipPath, ZipPathGround
1212

13-
from test.test_pathlib.support.local_path import ReadableLocalPath, LocalPathGround
14-
from test.test_pathlib.support.zip_path import ReadableZipPath, ZipPathGround
13+
if is_pypi:
14+
from pathlib_abc import PathInfo, _ReadablePath
15+
from pathlib_abc._os import magic_open
16+
else:
17+
from pathlib.types import PathInfo, _ReadablePath
18+
from pathlib._os import magic_open
1519

1620

1721
class ReadTestBase:
@@ -301,8 +305,11 @@ class LocalPathReadTest(ReadTestBase, unittest.TestCase):
301305
ground = LocalPathGround(ReadableLocalPath)
302306

303307

304-
class PathReadTest(ReadTestBase, unittest.TestCase):
305-
ground = LocalPathGround(Path)
308+
if not is_pypi:
309+
from pathlib import Path
310+
311+
class PathReadTest(ReadTestBase, unittest.TestCase):
312+
ground = LocalPathGround(Path)
306313

307314

308315
if __name__ == "__main__":

0 commit comments

Comments
 (0)