Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-65238: Add test cases for trailing slash handling in pathlib #113248

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,30 @@ def test_parent(self):
self.assertEqual(p.parent, P('//a/b/c'))
self.assertEqual(p.parent.parent, P('//a/b'))
self.assertEqual(p.parent.parent.parent, P('//a/b'))
# Trailing sep
p = P('z:/a/b/c/')
self.assertEqual(p.parent, P('z:/a/b'))
self.assertEqual(p.parent.parent, P('z:/a'))
self.assertEqual(p.parent.parent.parent, P('z:/'))
self.assertEqual(p.parent.parent.parent.parent, P('z:/'))

def test_parents(self):
# Anchored
P = self.cls
p = P('z:a/b')
par = p.parents
self.assertEqual(len(par), 2)
self.assertEqual(par[0], P('z:a'))
self.assertEqual(par[1], P('z:'))
self.assertEqual(par[0:1], (P('z:a'),))
self.assertEqual(par[:-1], (P('z:a'),))
self.assertEqual(par[:2], (P('z:a'), P('z:')))
self.assertEqual(par[1:], (P('z:'),))
self.assertEqual(par[::2], (P('z:a'),))
self.assertEqual(par[::-1], (P('z:'), P('z:a')))
self.assertEqual(list(par), [P('z:a'), P('z:')])
with self.assertRaises(IndexError):
par[2]
p = P('z:a/b/')
par = p.parents
self.assertEqual(len(par), 2)
Expand All @@ -455,6 +475,20 @@ def test_parents(self):
self.assertEqual(par[::2], (P('z:a'),))
self.assertEqual(par[::-1], (P('z:'), P('z:a')))
self.assertEqual(list(par), [P('z:a'), P('z:')])
with self.assertRaises(IndexError):
par[2]
p = P('z:/a/b')
par = p.parents
self.assertEqual(len(par), 2)
self.assertEqual(par[0], P('z:/a'))
self.assertEqual(par[1], P('z:/'))
self.assertEqual(par[0:1], (P('z:/a'),))
self.assertEqual(par[0:-1], (P('z:/a'),))
self.assertEqual(par[:2], (P('z:/a'), P('z:/')))
self.assertEqual(par[1:], (P('z:/'),))
self.assertEqual(par[::2], (P('z:/a'),))
self.assertEqual(par[::-1], (P('z:/'), P('z:/a'),))
self.assertEqual(list(par), [P('z:/a'), P('z:/')])
with self.assertRaises(IndexError):
par[2]
p = P('z:/a/b/')
Expand Down Expand Up @@ -522,18 +556,23 @@ def test_name(self):
self.assertEqual(P('c:').name, '')
self.assertEqual(P('c:/').name, '')
self.assertEqual(P('c:a/b').name, 'b')
self.assertEqual(P('c:a/b/').name, 'b')
self.assertEqual(P('c:/a/b').name, 'b')
self.assertEqual(P('c:/a/b/').name, 'b')
self.assertEqual(P('c:a/b.py').name, 'b.py')
self.assertEqual(P('c:/a/b.py').name, 'b.py')
self.assertEqual(P('//My.py/Share.php').name, '')
self.assertEqual(P('//My.py/Share.php/a/b').name, 'b')
self.assertEqual(P('c:/etc/cron.d/').name, 'cron.d')

def test_suffix(self):
P = self.cls
self.assertEqual(P('c:').suffix, '')
self.assertEqual(P('c:/').suffix, '')
self.assertEqual(P('c:a/b').suffix, '')
self.assertEqual(P('c:a/b/').suffix, '')
self.assertEqual(P('c:/a/b').suffix, '')
self.assertEqual(P('c:/a/b/').suffix, '')
self.assertEqual(P('c:a/b.py').suffix, '.py')
self.assertEqual(P('c:/a/b.py').suffix, '.py')
self.assertEqual(P('c:a/.hgrc').suffix, '')
Expand All @@ -546,13 +585,16 @@ def test_suffix(self):
self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffix, '')
self.assertEqual(P('//My.py/Share.php').suffix, '')
self.assertEqual(P('//My.py/Share.php/a/b').suffix, '')
self.assertEqual(P('c:/etc/cron.d/').suffix, '.d')

def test_suffixes(self):
P = self.cls
self.assertEqual(P('c:').suffixes, [])
self.assertEqual(P('c:/').suffixes, [])
self.assertEqual(P('c:a/b').suffixes, [])
self.assertEqual(P('c:a/b/').suffixes, [])
self.assertEqual(P('c:/a/b').suffixes, [])
self.assertEqual(P('c:/a/b/').suffixes, [])
self.assertEqual(P('c:a/b.py').suffixes, ['.py'])
self.assertEqual(P('c:/a/b.py').suffixes, ['.py'])
self.assertEqual(P('c:a/.hgrc').suffixes, [])
Expand All @@ -565,6 +607,7 @@ def test_suffixes(self):
self.assertEqual(P('//My.py/Share.php/a/b').suffixes, [])
self.assertEqual(P('c:a/Some name. Ending with a dot.').suffixes, [])
self.assertEqual(P('c:/a/Some name. Ending with a dot.').suffixes, [])
self.assertEqual(P('c:/etc/cron.d/').suffixes, ['.d'])

def test_stem(self):
P = self.cls
Expand All @@ -573,19 +616,22 @@ def test_stem(self):
self.assertEqual(P('c:..').stem, '..')
self.assertEqual(P('c:/').stem, '')
self.assertEqual(P('c:a/b').stem, 'b')
self.assertEqual(P('c:a/b/').stem, 'b')
self.assertEqual(P('c:a/b.py').stem, 'b')
self.assertEqual(P('c:a/.hgrc').stem, '.hgrc')
self.assertEqual(P('c:a/.hg.rc').stem, '.hg')
self.assertEqual(P('c:a/b.tar.gz').stem, 'b.tar')
self.assertEqual(P('c:a/Some name. Ending with a dot.').stem,
'Some name. Ending with a dot.')
self.assertEqual(P('c:/etc/cron.d/').stem, 'cron')

def test_with_name(self):
P = self.cls
self.assertEqual(P('c:a/b').with_name('d.xml'), P('c:a/d.xml'))
self.assertEqual(P('c:/a/b').with_name('d.xml'), P('c:/a/d.xml'))
self.assertEqual(P('c:a/Dot ending.').with_name('d.xml'), P('c:a/d.xml'))
self.assertEqual(P('c:/a/Dot ending.').with_name('d.xml'), P('c:/a/d.xml'))
self.assertEqual(P('c:/etc/cron.d/').with_name('tron.g'), P('c:/etc/tron.g/'))
self.assertRaises(ValueError, P('c:').with_name, 'd.xml')
self.assertRaises(ValueError, P('c:/').with_name, 'd.xml')
self.assertRaises(ValueError, P('//My/Share').with_name, 'd.xml')
Expand All @@ -602,6 +648,7 @@ def test_with_stem(self):
self.assertEqual(P('c:/a/b').with_stem('d'), P('c:/a/d'))
self.assertEqual(P('c:a/Dot ending.').with_stem('d'), P('c:a/d'))
self.assertEqual(P('c:/a/Dot ending.').with_stem('d'), P('c:/a/d'))
self.assertEqual(P('c:/etc/cron.d/').with_stem('tron'), P('c:/etc/tron.d/'))
self.assertRaises(ValueError, P('c:').with_stem, 'd')
self.assertRaises(ValueError, P('c:/').with_stem, 'd')
self.assertRaises(ValueError, P('//My/Share').with_stem, 'd')
Expand All @@ -618,6 +665,7 @@ def test_with_suffix(self):
self.assertEqual(P('c:/a/b').with_suffix('.gz'), P('c:/a/b.gz'))
self.assertEqual(P('c:a/b.py').with_suffix('.gz'), P('c:a/b.gz'))
self.assertEqual(P('c:/a/b.py').with_suffix('.gz'), P('c:/a/b.gz'))
self.assertEqual(P('c:/etc/cron.d/').with_suffix('.g'), P('c:/etc/cron.g/'))
# Path doesn't have a "filename" component.
self.assertRaises(ValueError, P('').with_suffix, '.gz')
self.assertRaises(ValueError, P('.').with_suffix, '.gz')
Expand Down Expand Up @@ -1018,6 +1066,12 @@ def test_expanduser_common(self):
P = self.cls
p = P('~')
self.assertEqual(p.expanduser(), P(os.path.expanduser('~')))
p = P('~/')
self.assertEqual(p.expanduser(), P(os.path.expanduser('~/')))
p = P('~/foo')
self.assertEqual(p.expanduser(), P(os.path.expanduser('~/foo')))
p = P('~/foo/')
self.assertEqual(p.expanduser(), P(os.path.expanduser('~/foo/')))
p = P('foo')
self.assertEqual(p.expanduser(), p)
p = P('/~')
Expand Down
80 changes: 80 additions & 0 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,12 @@ def test_parent_common(self):
self.assertEqual(p.parent.parent, P('/a'))
self.assertEqual(p.parent.parent.parent, P('/'))
self.assertEqual(p.parent.parent.parent.parent, P('/'))
# Trailing sep
p = P('/a/b/c/')
self.assertEqual(p.parent, P('/a/b'))
self.assertEqual(p.parent.parent, P('/a'))
self.assertEqual(p.parent.parent.parent, P('/'))
self.assertEqual(p.parent.parent.parent.parent, P('/'))

def test_parents_common(self):
# Relative
Expand Down Expand Up @@ -412,6 +418,27 @@ def test_parents_common(self):
par[-4]
with self.assertRaises(IndexError):
par[3]
# Trailing sep
p = P('/a/b/c/')
par = p.parents
self.assertEqual(len(par), 3)
self.assertEqual(par[0], P('/a/b'))
self.assertEqual(par[1], P('/a'))
self.assertEqual(par[2], P('/'))
self.assertEqual(par[-1], P('/'))
self.assertEqual(par[-2], P('/a'))
self.assertEqual(par[-3], P('/a/b'))
self.assertEqual(par[0:1], (P('/a/b'),))
self.assertEqual(par[:2], (P('/a/b'), P('/a')))
self.assertEqual(par[:-1], (P('/a/b'), P('/a')))
self.assertEqual(par[1:], (P('/a'), P('/')))
self.assertEqual(par[::2], (P('/a/b'), P('/')))
self.assertEqual(par[::-1], (P('/'), P('/a'), P('/a/b')))
self.assertEqual(list(par), [P('/a/b'), P('/a'), P('/')])
with self.assertRaises(IndexError):
par[-4]
with self.assertRaises(IndexError):
par[3]

def test_drive_common(self):
P = self.cls
Expand Down Expand Up @@ -441,10 +468,13 @@ def test_name_common(self):
self.assertEqual(P('.').name, '')
self.assertEqual(P('/').name, '')
self.assertEqual(P('a/b').name, 'b')
self.assertEqual(P('a/b/').name, 'b')
self.assertEqual(P('/a/b').name, 'b')
self.assertEqual(P('/a/b/').name, 'b')
self.assertEqual(P('/a/b/.').name, 'b')
self.assertEqual(P('a/b.py').name, 'b.py')
self.assertEqual(P('/a/b.py').name, 'b.py')
self.assertEqual(P('/etc/cron.d/').name, 'cron.d')

def test_suffix_common(self):
P = self.cls
Expand All @@ -453,7 +483,9 @@ def test_suffix_common(self):
self.assertEqual(P('..').suffix, '')
self.assertEqual(P('/').suffix, '')
self.assertEqual(P('a/b').suffix, '')
self.assertEqual(P('a/b/').suffix, '')
self.assertEqual(P('/a/b').suffix, '')
self.assertEqual(P('/a/b/').suffix, '')
self.assertEqual(P('/a/b/.').suffix, '')
self.assertEqual(P('a/b.py').suffix, '.py')
self.assertEqual(P('/a/b.py').suffix, '.py')
Expand All @@ -465,14 +497,17 @@ def test_suffix_common(self):
self.assertEqual(P('/a/b.tar.gz').suffix, '.gz')
self.assertEqual(P('a/Some name. Ending with a dot.').suffix, '')
self.assertEqual(P('/a/Some name. Ending with a dot.').suffix, '')
self.assertEqual(P('/etc/cron.d/').suffix, '.d')

def test_suffixes_common(self):
P = self.cls
self.assertEqual(P('').suffixes, [])
self.assertEqual(P('.').suffixes, [])
self.assertEqual(P('/').suffixes, [])
self.assertEqual(P('a/b').suffixes, [])
self.assertEqual(P('a/b/').suffixes, [])
self.assertEqual(P('/a/b').suffixes, [])
self.assertEqual(P('/a/b/').suffixes, [])
self.assertEqual(P('/a/b/.').suffixes, [])
self.assertEqual(P('a/b.py').suffixes, ['.py'])
self.assertEqual(P('/a/b.py').suffixes, ['.py'])
Expand All @@ -484,6 +519,7 @@ def test_suffixes_common(self):
self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz'])
self.assertEqual(P('a/Some name. Ending with a dot.').suffixes, [])
self.assertEqual(P('/a/Some name. Ending with a dot.').suffixes, [])
self.assertEqual(P('/etc/cron.d/').suffixes, ['.d'])

def test_stem_common(self):
P = self.cls
Expand All @@ -492,12 +528,14 @@ def test_stem_common(self):
self.assertEqual(P('..').stem, '..')
self.assertEqual(P('/').stem, '')
self.assertEqual(P('a/b').stem, 'b')
self.assertEqual(P('a/b/').stem, 'b')
self.assertEqual(P('a/b.py').stem, 'b')
self.assertEqual(P('a/.hgrc').stem, '.hgrc')
self.assertEqual(P('a/.hg.rc').stem, '.hg')
self.assertEqual(P('a/b.tar.gz').stem, 'b.tar')
self.assertEqual(P('a/Some name. Ending with a dot.').stem,
'Some name. Ending with a dot.')
self.assertEqual(P('/etc/cron.d/').stem, 'cron')

def test_with_name_common(self):
P = self.cls
Expand All @@ -507,6 +545,7 @@ def test_with_name_common(self):
self.assertEqual(P('/a/b.py').with_name('d.xml'), P('/a/d.xml'))
self.assertEqual(P('a/Dot ending.').with_name('d.xml'), P('a/d.xml'))
self.assertEqual(P('/a/Dot ending.').with_name('d.xml'), P('/a/d.xml'))
self.assertEqual(P('/etc/cron.d/').with_name('tron.g'), P('/etc/tron.g/'))
self.assertRaises(ValueError, P('').with_name, 'd.xml')
self.assertRaises(ValueError, P('.').with_name, 'd.xml')
self.assertRaises(ValueError, P('/').with_name, 'd.xml')
Expand All @@ -525,6 +564,7 @@ def test_with_stem_common(self):
self.assertEqual(P('/a/b.tar.gz').with_stem('d'), P('/a/d.gz'))
self.assertEqual(P('a/Dot ending.').with_stem('d'), P('a/d'))
self.assertEqual(P('/a/Dot ending.').with_stem('d'), P('/a/d'))
self.assertEqual(P('/etc/cron.d/').with_stem('tron'), P('/etc/tron.d/'))
self.assertRaises(ValueError, P('').with_stem, 'd')
self.assertRaises(ValueError, P('.').with_stem, 'd')
self.assertRaises(ValueError, P('/').with_stem, 'd')
Expand All @@ -540,9 +580,11 @@ def test_with_suffix_common(self):
self.assertEqual(P('/a/b').with_suffix('.gz'), P('/a/b.gz'))
self.assertEqual(P('a/b.py').with_suffix('.gz'), P('a/b.gz'))
self.assertEqual(P('/a/b.py').with_suffix('.gz'), P('/a/b.gz'))
self.assertEqual(P('/etc/cron.d/').with_suffix('.g'), P('/etc/cron.g/'))
# Stripping suffix.
self.assertEqual(P('a/b.py').with_suffix(''), P('a/b'))
self.assertEqual(P('/a/b').with_suffix(''), P('/a/b'))
self.assertEqual(P('/etc/cron.d/').with_suffix(''), P('/etc/cron/'))
# Path doesn't have a "filename" component.
self.assertRaises(ValueError, P('').with_suffix, '.gz')
self.assertRaises(ValueError, P('.').with_suffix, '.gz')
Expand Down Expand Up @@ -636,6 +678,25 @@ def test_relative_to_common(self):
self.assertRaises(ValueError, p.relative_to, P("a/.."), walk_up=True)
self.assertRaises(ValueError, p.relative_to, P("/a/.."), walk_up=True)

def test_relative_to_trailing_sep(self):
P = self.cls
self.assertEqual(P('foo').relative_to('foo'), P())
self.assertEqual(P('foo').relative_to('foo/'), P())
self.assertEqual(P('foo/').relative_to('foo'), P())
self.assertEqual(P('foo/').relative_to('foo/'), P())
self.assertEqual(P('foo/bar').relative_to('foo'), P('bar'))
self.assertEqual(P('foo/bar').relative_to('foo/'), P('bar'))
self.assertEqual(P('foo/bar/').relative_to('foo'), P('bar/'))
self.assertEqual(P('foo/bar/').relative_to('foo/'), P('bar/'))
self.assertEqual(P('foo').relative_to('foo/bar', walk_up=True), P('..'))
self.assertEqual(P('foo').relative_to('foo/bar/', walk_up=True), P('..'))
self.assertEqual(P('foo/').relative_to('foo/bar', walk_up=True), P('../'))
self.assertEqual(P('foo/').relative_to('foo/bar/', walk_up=True), P('../'))
self.assertEqual(P('foo/oof').relative_to('foo/bar', walk_up=True), P('../oof'))
self.assertEqual(P('foo/oof').relative_to('foo/bar/', walk_up=True), P('../oof'))
self.assertEqual(P('foo/oof/').relative_to('foo/bar', walk_up=True), P('../oof/'))
self.assertEqual(P('foo/oof/').relative_to('foo/bar/', walk_up=True), P('../oof/'))

def test_is_relative_to_common(self):
P = self.cls
p = P('a/b')
Expand Down Expand Up @@ -671,6 +732,25 @@ def test_is_relative_to_common(self):
self.assertFalse(p.is_relative_to(''))
self.assertFalse(p.is_relative_to(P('a')))

def test_is_relative_to_trailing_sep(self):
P = self.cls
self.assertTrue(P('foo').is_relative_to('foo'))
self.assertTrue(P('foo').is_relative_to('foo/'))
self.assertTrue(P('foo/').is_relative_to('foo'))
self.assertTrue(P('foo/').is_relative_to('foo/'))
self.assertTrue(P('foo/bar').is_relative_to('foo'))
self.assertTrue(P('foo/bar').is_relative_to('foo/'))
self.assertTrue(P('foo/bar/').is_relative_to('foo'))
self.assertTrue(P('foo/bar/').is_relative_to('foo/'))
self.assertFalse(P('foo').is_relative_to('foo/bar'))
self.assertFalse(P('foo').is_relative_to('foo/bar/'))
self.assertFalse(P('foo/').is_relative_to('foo/bar'))
self.assertFalse(P('foo/').is_relative_to('foo/bar/'))
self.assertFalse(P('foo/oof').is_relative_to('foo/bar'))
self.assertFalse(P('foo/oof').is_relative_to('foo/bar/'))
self.assertFalse(P('foo/oof/').is_relative_to('foo/bar'))
self.assertFalse(P('foo/oof/').is_relative_to('foo/bar/'))


#
# Tests for the virtual classes.
Expand Down
Loading