Skip to content

Commit

Permalink
pythonGH-65238: Add test cases for trailing slash handling in pathlib
Browse files Browse the repository at this point in the history
Ensure that trailing slashes are ignored whenever pathlib splits a basename
from a dirname. This commit adds test cases for `parent`, `parents`,
`name`, `stem`, `suffix`, `suffixes`, `with_name()`, `with_stem()`,
`with_suffix()`, `relative_to()`, `is_relative_to()`, `expanduser()` and
`absolute()`.

Any solution for pythonGH-65238 should keep these tests passing.
  • Loading branch information
barneygale committed Dec 18, 2023
1 parent 2f0ec7f commit 2c08523
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 0 deletions.
57 changes: 57 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 Expand Up @@ -1797,10 +1851,12 @@ def test_absolute(self):
# Relative path with root
self.assertEqual(str(P('\\').absolute()), drive + '\\')
self.assertEqual(str(P('\\foo').absolute()), drive + '\\foo')
self.assertEqual(str(P('\\foo\\').absolute()), drive + '\\foo\\')

# Relative path on current drive
self.assertEqual(str(P(drive).absolute()), self.base)
self.assertEqual(str(P(drive + 'foo').absolute()), os.path.join(self.base, 'foo'))
self.assertEqual(str(P(drive + 'foo\\').absolute()), os.path.join(self.base, 'foo\\'))

with os_helper.subst_drive(self.base) as other_drive:
# Set the working directory on the substitute drive
Expand All @@ -1812,6 +1868,7 @@ def test_absolute(self):
# Relative path on another drive
self.assertEqual(str(P(other_drive).absolute()), other_cwd)
self.assertEqual(str(P(other_drive + 'foo').absolute()), other_cwd + '\\foo')
self.assertEqual(str(P(other_drive + 'foo\\').absolute()), other_cwd + '\\foo\\')

def test_glob(self):
P = self.cls
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

0 comments on commit 2c08523

Please sign in to comment.