Skip to content

Commit 4bdc13c

Browse files
barneygaleseehwan80
authored andcommitted
pythonGH-130614: pathlib ABCs: revise test suite for Windows path joining (python#131016)
Test Windows-flavoured `pathlib.types._JoinablePath` in a dedicated test module. These tests cover `LexicalWindowsPath`, `PureWindowsPath` and `WindowsPath`, where `LexicalWindowsPath` is a simple implementation of `_JoinablePath` for use in tests.
1 parent aecf05c commit 4bdc13c

File tree

4 files changed

+320
-279
lines changed

4 files changed

+320
-279
lines changed

Lib/test/test_pathlib/support/lexical_path.py

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Simple implementation of JoinablePath, for use in pathlib tests.
33
"""
44

5+
import ntpath
56
import os.path
67
import pathlib.types
78
import posixpath
@@ -37,3 +38,8 @@ def with_segments(self, *pathsegments):
3738
class LexicalPosixPath(LexicalPath):
3839
__slots__ = ()
3940
parser = posixpath
41+
42+
43+
class LexicalWindowsPath(LexicalPath):
44+
__slots__ = ()
45+
parser = ntpath
+290
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
"""
2+
Tests for Windows-flavoured pathlib.types._JoinablePath
3+
"""
4+
5+
import os
6+
import unittest
7+
8+
from pathlib import PureWindowsPath, WindowsPath
9+
from test.test_pathlib.support.lexical_path import LexicalWindowsPath
10+
11+
12+
class JoinTestBase:
13+
def test_join(self):
14+
P = self.cls
15+
p = P('C:/a/b')
16+
pp = p.joinpath('x/y')
17+
self.assertEqual(pp, P(r'C:/a/b\x/y'))
18+
pp = p.joinpath('/x/y')
19+
self.assertEqual(pp, P('C:/x/y'))
20+
# Joining with a different drive => the first path is ignored, even
21+
# if the second path is relative.
22+
pp = p.joinpath('D:x/y')
23+
self.assertEqual(pp, P('D:x/y'))
24+
pp = p.joinpath('D:/x/y')
25+
self.assertEqual(pp, P('D:/x/y'))
26+
pp = p.joinpath('//host/share/x/y')
27+
self.assertEqual(pp, P('//host/share/x/y'))
28+
# Joining with the same drive => the first path is appended to if
29+
# the second path is relative.
30+
pp = p.joinpath('c:x/y')
31+
self.assertEqual(pp, P(r'c:/a/b\x/y'))
32+
pp = p.joinpath('c:/x/y')
33+
self.assertEqual(pp, P('c:/x/y'))
34+
# Joining with files with NTFS data streams => the filename should
35+
# not be parsed as a drive letter
36+
pp = p.joinpath('./d:s')
37+
self.assertEqual(pp, P(r'C:/a/b\./d:s'))
38+
pp = p.joinpath('./dd:s')
39+
self.assertEqual(pp, P(r'C:/a/b\./dd:s'))
40+
pp = p.joinpath('E:d:s')
41+
self.assertEqual(pp, P('E:d:s'))
42+
# Joining onto a UNC path with no root
43+
pp = P('//').joinpath('server')
44+
self.assertEqual(pp, P('//server'))
45+
pp = P('//server').joinpath('share')
46+
self.assertEqual(pp, P(r'//server\share'))
47+
pp = P('//./BootPartition').joinpath('Windows')
48+
self.assertEqual(pp, P(r'//./BootPartition\Windows'))
49+
50+
def test_div(self):
51+
# Basically the same as joinpath().
52+
P = self.cls
53+
p = P('C:/a/b')
54+
self.assertEqual(p / 'x/y', P(r'C:/a/b\x/y'))
55+
self.assertEqual(p / 'x' / 'y', P(r'C:/a/b\x\y'))
56+
self.assertEqual(p / '/x/y', P('C:/x/y'))
57+
self.assertEqual(p / '/x' / 'y', P('C:/x\y'))
58+
# Joining with a different drive => the first path is ignored, even
59+
# if the second path is relative.
60+
self.assertEqual(p / 'D:x/y', P('D:x/y'))
61+
self.assertEqual(p / 'D:' / 'x/y', P('D:x/y'))
62+
self.assertEqual(p / 'D:/x/y', P('D:/x/y'))
63+
self.assertEqual(p / 'D:' / '/x/y', P('D:/x/y'))
64+
self.assertEqual(p / '//host/share/x/y', P('//host/share/x/y'))
65+
# Joining with the same drive => the first path is appended to if
66+
# the second path is relative.
67+
self.assertEqual(p / 'c:x/y', P(r'c:/a/b\x/y'))
68+
self.assertEqual(p / 'c:/x/y', P('c:/x/y'))
69+
# Joining with files with NTFS data streams => the filename should
70+
# not be parsed as a drive letter
71+
self.assertEqual(p / './d:s', P(r'C:/a/b\./d:s'))
72+
self.assertEqual(p / './dd:s', P(r'C:/a/b\./dd:s'))
73+
self.assertEqual(p / 'E:d:s', P('E:d:s'))
74+
75+
def test_str(self):
76+
p = self.cls(r'a\b\c')
77+
self.assertEqual(str(p), 'a\\b\\c')
78+
p = self.cls(r'c:\a\b\c')
79+
self.assertEqual(str(p), 'c:\\a\\b\\c')
80+
p = self.cls('\\\\a\\b\\')
81+
self.assertEqual(str(p), '\\\\a\\b\\')
82+
p = self.cls(r'\\a\b\c')
83+
self.assertEqual(str(p), '\\\\a\\b\\c')
84+
p = self.cls(r'\\a\b\c\d')
85+
self.assertEqual(str(p), '\\\\a\\b\\c\\d')
86+
87+
def test_parts(self):
88+
P = self.cls
89+
p = P(r'c:a\b')
90+
parts = p.parts
91+
self.assertEqual(parts, ('c:', 'a', 'b'))
92+
p = P(r'c:\a\b')
93+
parts = p.parts
94+
self.assertEqual(parts, ('c:\\', 'a', 'b'))
95+
p = P(r'\\a\b\c\d')
96+
parts = p.parts
97+
self.assertEqual(parts, ('\\\\a\\b\\', 'c', 'd'))
98+
99+
def test_parent(self):
100+
# Anchored
101+
P = self.cls
102+
p = P('z:a/b/c')
103+
self.assertEqual(p.parent, P('z:a/b'))
104+
self.assertEqual(p.parent.parent, P('z:a'))
105+
self.assertEqual(p.parent.parent.parent, P('z:'))
106+
self.assertEqual(p.parent.parent.parent.parent, P('z:'))
107+
p = P('z:/a/b/c')
108+
self.assertEqual(p.parent, P('z:/a/b'))
109+
self.assertEqual(p.parent.parent, P('z:/a'))
110+
self.assertEqual(p.parent.parent.parent, P('z:/'))
111+
self.assertEqual(p.parent.parent.parent.parent, P('z:/'))
112+
p = P('//a/b/c/d')
113+
self.assertEqual(p.parent, P('//a/b/c'))
114+
self.assertEqual(p.parent.parent, P('//a/b/'))
115+
self.assertEqual(p.parent.parent.parent, P('//a/b/'))
116+
117+
def test_parents(self):
118+
# Anchored
119+
P = self.cls
120+
p = P('z:a/b')
121+
par = p.parents
122+
self.assertEqual(len(par), 2)
123+
self.assertEqual(par[0], P('z:a'))
124+
self.assertEqual(par[1], P('z:'))
125+
self.assertEqual(par[0:1], (P('z:a'),))
126+
self.assertEqual(par[:-1], (P('z:a'),))
127+
self.assertEqual(par[:2], (P('z:a'), P('z:')))
128+
self.assertEqual(par[1:], (P('z:'),))
129+
self.assertEqual(par[::2], (P('z:a'),))
130+
self.assertEqual(par[::-1], (P('z:'), P('z:a')))
131+
self.assertEqual(list(par), [P('z:a'), P('z:')])
132+
with self.assertRaises(IndexError):
133+
par[2]
134+
p = P('z:/a/b')
135+
par = p.parents
136+
self.assertEqual(len(par), 2)
137+
self.assertEqual(par[0], P('z:/a'))
138+
self.assertEqual(par[1], P('z:/'))
139+
self.assertEqual(par[0:1], (P('z:/a'),))
140+
self.assertEqual(par[0:-1], (P('z:/a'),))
141+
self.assertEqual(par[:2], (P('z:/a'), P('z:/')))
142+
self.assertEqual(par[1:], (P('z:/'),))
143+
self.assertEqual(par[::2], (P('z:/a'),))
144+
self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),))
145+
self.assertEqual(list(par), [P('z:/a'), P('z:/')])
146+
with self.assertRaises(IndexError):
147+
par[2]
148+
p = P('//a/b/c/d')
149+
par = p.parents
150+
self.assertEqual(len(par), 2)
151+
self.assertEqual(par[0], P('//a/b/c'))
152+
self.assertEqual(par[1], P('//a/b/'))
153+
self.assertEqual(par[0:1], (P('//a/b/c'),))
154+
self.assertEqual(par[0:-1], (P('//a/b/c'),))
155+
self.assertEqual(par[:2], (P('//a/b/c'), P('//a/b/')))
156+
self.assertEqual(par[1:], (P('//a/b/'),))
157+
self.assertEqual(par[::2], (P('//a/b/c'),))
158+
self.assertEqual(par[::-1], (P('//a/b/'), P('//a/b/c')))
159+
self.assertEqual(list(par), [P('//a/b/c'), P('//a/b/')])
160+
with self.assertRaises(IndexError):
161+
par[2]
162+
163+
def test_anchor(self):
164+
P = self.cls
165+
self.assertEqual(P('c:').anchor, 'c:')
166+
self.assertEqual(P('c:a/b').anchor, 'c:')
167+
self.assertEqual(P('c:\\').anchor, 'c:\\')
168+
self.assertEqual(P('c:\\a\\b\\').anchor, 'c:\\')
169+
self.assertEqual(P('\\\\a\\b\\').anchor, '\\\\a\\b\\')
170+
self.assertEqual(P('\\\\a\\b\\c\\d').anchor, '\\\\a\\b\\')
171+
172+
def test_name(self):
173+
P = self.cls
174+
self.assertEqual(P('c:').name, '')
175+
self.assertEqual(P('c:/').name, '')
176+
self.assertEqual(P('c:a/b').name, 'b')
177+
self.assertEqual(P('c:/a/b').name, 'b')
178+
self.assertEqual(P('c:a/b.py').name, 'b.py')
179+
self.assertEqual(P('c:/a/b.py').name, 'b.py')
180+
self.assertEqual(P('//My.py/Share.php').name, '')
181+
self.assertEqual(P('//My.py/Share.php/a/b').name, 'b')
182+
183+
def test_stem(self):
184+
P = self.cls
185+
self.assertEqual(P('c:').stem, '')
186+
self.assertEqual(P('c:..').stem, '..')
187+
self.assertEqual(P('c:/').stem, '')
188+
self.assertEqual(P('c:a/b').stem, 'b')
189+
self.assertEqual(P('c:a/b.py').stem, 'b')
190+
self.assertEqual(P('c:a/.hgrc').stem, '.hgrc')
191+
self.assertEqual(P('c:a/.hg.rc').stem, '.hg')
192+
self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar')
193+
self.assertEqual(P('c:a/trailing.dot.').stem, 'trailing.dot')
194+
195+
def test_suffix(self):
196+
P = self.cls
197+
self.assertEqual(P('c:').suffix, '')
198+
self.assertEqual(P('c:/').suffix, '')
199+
self.assertEqual(P('c:a/b').suffix, '')
200+
self.assertEqual(P('c:/a/b').suffix, '')
201+
self.assertEqual(P('c:a/b.py').suffix, '.py')
202+
self.assertEqual(P('c:/a/b.py').suffix, '.py')
203+
self.assertEqual(P('c:a/.hgrc').suffix, '')
204+
self.assertEqual(P('c:/a/.hgrc').suffix, '')
205+
self.assertEqual(P('c:a/.hg.rc').suffix, '.rc')
206+
self.assertEqual(P('c:/a/.hg.rc').suffix, '.rc')
207+
self.assertEqual(P('c:a/b.tar.gz').suffix, '.gz')
208+
self.assertEqual(P('c:/a/b.tar.gz').suffix, '.gz')
209+
self.assertEqual(P('c:a/trailing.dot.').suffix, '.')
210+
self.assertEqual(P('c:/a/trailing.dot.').suffix, '.')
211+
self.assertEqual(P('//My.py/Share.php').suffix, '')
212+
self.assertEqual(P('//My.py/Share.php/a/b').suffix, '')
213+
214+
def test_suffixes(self):
215+
P = self.cls
216+
self.assertEqual(P('c:').suffixes, [])
217+
self.assertEqual(P('c:/').suffixes, [])
218+
self.assertEqual(P('c:a/b').suffixes, [])
219+
self.assertEqual(P('c:/a/b').suffixes, [])
220+
self.assertEqual(P('c:a/b.py').suffixes, ['.py'])
221+
self.assertEqual(P('c:/a/b.py').suffixes, ['.py'])
222+
self.assertEqual(P('c:a/.hgrc').suffixes, [])
223+
self.assertEqual(P('c:/a/.hgrc').suffixes, [])
224+
self.assertEqual(P('c:a/.hg.rc').suffixes, ['.rc'])
225+
self.assertEqual(P('c:/a/.hg.rc').suffixes, ['.rc'])
226+
self.assertEqual(P('c:a/b.tar.gz').suffixes, ['.tar', '.gz'])
227+
self.assertEqual(P('c:/a/b.tar.gz').suffixes, ['.tar', '.gz'])
228+
self.assertEqual(P('//My.py/Share.php').suffixes, [])
229+
self.assertEqual(P('//My.py/Share.php/a/b').suffixes, [])
230+
self.assertEqual(P('c:a/trailing.dot.').suffixes, ['.dot', '.'])
231+
self.assertEqual(P('c:/a/trailing.dot.').suffixes, ['.dot', '.'])
232+
233+
def test_with_name(self):
234+
P = self.cls
235+
self.assertEqual(P(r'c:a\b').with_name('d.xml'), P(r'c:a\d.xml'))
236+
self.assertEqual(P(r'c:\a\b').with_name('d.xml'), P(r'c:\a\d.xml'))
237+
self.assertEqual(P(r'c:a\Dot ending.').with_name('d.xml'), P(r'c:a\d.xml'))
238+
self.assertEqual(P(r'c:\a\Dot ending.').with_name('d.xml'), P(r'c:\a\d.xml'))
239+
self.assertRaises(ValueError, P(r'c:a\b').with_name, r'd:\e')
240+
self.assertRaises(ValueError, P(r'c:a\b').with_name, r'\\My\Share')
241+
242+
def test_with_stem(self):
243+
P = self.cls
244+
self.assertEqual(P('c:a/b').with_stem('d'), P('c:a/d'))
245+
self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d'))
246+
self.assertEqual(P('c:a/Dot ending.').with_stem('d'), P('c:a/d.'))
247+
self.assertEqual(P('c:/a/Dot ending.').with_stem('d'), P('c:/a/d.'))
248+
self.assertRaises(ValueError, P('c:a/b').with_stem, 'd:/e')
249+
self.assertRaises(ValueError, P('c:a/b').with_stem, '//My/Share')
250+
251+
def test_with_suffix(self):
252+
P = self.cls
253+
self.assertEqual(P('c:a/b').with_suffix('.gz'), P('c:a/b.gz'))
254+
self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz'))
255+
self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz'))
256+
self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz'))
257+
# Path doesn't have a "filename" component.
258+
self.assertRaises(ValueError, P('').with_suffix, '.gz')
259+
self.assertRaises(ValueError, P('/').with_suffix, '.gz')
260+
self.assertRaises(ValueError, P('//My/Share').with_suffix, '.gz')
261+
# Invalid suffix.
262+
self.assertRaises(ValueError, P('c:a/b').with_suffix, 'gz')
263+
self.assertRaises(ValueError, P('c:a/b').with_suffix, '/')
264+
self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\')
265+
self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:')
266+
self.assertRaises(ValueError, P('c:a/b').with_suffix, '/.gz')
267+
self.assertRaises(ValueError, P('c:a/b').with_suffix, '\\.gz')
268+
self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c:.gz')
269+
self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c/d')
270+
self.assertRaises(ValueError, P('c:a/b').with_suffix, 'c\\d')
271+
self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c/d')
272+
self.assertRaises(ValueError, P('c:a/b').with_suffix, '.c\\d')
273+
self.assertRaises(TypeError, P('c:a/b').with_suffix, None)
274+
275+
276+
class LexicalWindowsPathJoinTest(JoinTestBase, unittest.TestCase):
277+
cls = LexicalWindowsPath
278+
279+
280+
class PureWindowsPathJoinTest(JoinTestBase, unittest.TestCase):
281+
cls = PureWindowsPath
282+
283+
284+
if os.name == 'nt':
285+
class WindowsPathJoinTest(JoinTestBase, unittest.TestCase):
286+
cls = WindowsPath
287+
288+
289+
if __name__ == "__main__":
290+
unittest.main()

Lib/test/test_pathlib/test_pathlib.py

+24
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,18 @@ def test_stem_empty(self):
411411
self.assertEqual(P('').stem, '')
412412
self.assertEqual(P('.').stem, '')
413413

414+
@needs_windows
415+
def test_with_name_windows(self):
416+
P = self.cls
417+
self.assertRaises(ValueError, P(r'c:').with_name, 'd.xml')
418+
self.assertRaises(ValueError, P(r'c:\\').with_name, 'd.xml')
419+
self.assertRaises(ValueError, P(r'\\My\Share').with_name, 'd.xml')
420+
# NTFS alternate data streams
421+
self.assertEqual(str(P('a').with_name('d:')), '.\\d:')
422+
self.assertEqual(str(P('a').with_name('d:e')), '.\\d:e')
423+
self.assertEqual(P(r'c:a\b').with_name('d:'), P(r'c:a\d:'))
424+
self.assertEqual(P(r'c:a\b').with_name('d:e'), P(r'c:a\d:e'))
425+
414426
def test_with_name_empty(self):
415427
P = self.cls
416428
self.assertRaises(ValueError, P('').with_name, 'd.xml')
@@ -419,6 +431,18 @@ def test_with_name_empty(self):
419431
self.assertRaises(ValueError, P('a/b').with_name, '')
420432
self.assertRaises(ValueError, P('a/b').with_name, '.')
421433

434+
@needs_windows
435+
def test_with_stem_windows(self):
436+
P = self.cls
437+
self.assertRaises(ValueError, P('c:').with_stem, 'd')
438+
self.assertRaises(ValueError, P('c:/').with_stem, 'd')
439+
self.assertRaises(ValueError, P('//My/Share').with_stem, 'd')
440+
# NTFS alternate data streams
441+
self.assertEqual(str(P('a').with_stem('d:')), '.\\d:')
442+
self.assertEqual(str(P('a').with_stem('d:e')), '.\\d:e')
443+
self.assertEqual(P('c:a/b').with_stem('d:'), P('c:a/d:'))
444+
self.assertEqual(P('c:a/b').with_stem('d:e'), P('c:a/d:e'))
445+
422446
def test_with_stem_empty(self):
423447
P = self.cls
424448
self.assertRaises(ValueError, P('').with_stem, 'd')

0 commit comments

Comments
 (0)