summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_pathlib/test_join.py
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2025-03-09 17:02:37 (GMT)
committerGitHub <noreply@github.com>2025-03-09 17:02:37 (GMT)
commit475f933ed8b1c9546f1b5497a2241140c7065b5f (patch)
treed09ed5bdbee1ee536ff46e52ba19ce8ac5889761 /Lib/test/test_pathlib/test_join.py
parentbbd6d17ef85e5de885cdfab0ae567184ba1550ff (diff)
downloadcpython-475f933ed8b1c9546f1b5497a2241140c7065b5f.zip
cpython-475f933ed8b1c9546f1b5497a2241140c7065b5f.tar.gz
cpython-475f933ed8b1c9546f1b5497a2241140c7065b5f.tar.bz2
GH-130614: pathlib ABCs: revise test suite for path joining (#130988)
Test `pathlib.types._JoinablePath` in a dedicated test module. These tests cover `LexicalPath`, `PurePath` and `Path`, where `LexicalPath` is defined in a new `test.test_pathlib.support` package.
Diffstat (limited to 'Lib/test/test_pathlib/test_join.py')
-rw-r--r--Lib/test/test_pathlib/test_join.py372
1 files changed, 372 insertions, 0 deletions
diff --git a/Lib/test/test_pathlib/test_join.py b/Lib/test/test_pathlib/test_join.py
new file mode 100644
index 0000000..93fd1e6
--- /dev/null
+++ b/Lib/test/test_pathlib/test_join.py
@@ -0,0 +1,372 @@
+"""
+Tests for pathlib.types._JoinablePath
+"""
+
+import unittest
+
+from pathlib import PurePath, Path
+from pathlib.types import _PathParser, _JoinablePath
+from test.test_pathlib.support.lexical_path import LexicalPath
+
+
+class JoinTestBase:
+ def test_is_joinable(self):
+ p = self.cls()
+ self.assertIsInstance(p, _JoinablePath)
+
+ def test_parser(self):
+ self.assertIsInstance(self.cls.parser, _PathParser)
+
+ def test_constructor(self):
+ P = self.cls
+ p = P('a')
+ self.assertIsInstance(p, P)
+ P()
+ P('a', 'b', 'c')
+ P('/a', 'b', 'c')
+ P('a/b/c')
+ P('/a/b/c')
+
+ def test_with_segments(self):
+ class P(self.cls):
+ def __init__(self, *pathsegments, session_id):
+ super().__init__(*pathsegments)
+ self.session_id = session_id
+
+ def with_segments(self, *pathsegments):
+ return type(self)(*pathsegments, session_id=self.session_id)
+ p = P('foo', 'bar', session_id=42)
+ self.assertEqual(42, (p / 'foo').session_id)
+ self.assertEqual(42, ('foo' / p).session_id)
+ self.assertEqual(42, p.joinpath('foo').session_id)
+ self.assertEqual(42, p.with_name('foo').session_id)
+ self.assertEqual(42, p.with_stem('foo').session_id)
+ self.assertEqual(42, p.with_suffix('.foo').session_id)
+ self.assertEqual(42, p.with_segments('foo').session_id)
+ self.assertEqual(42, p.parent.session_id)
+ for parent in p.parents:
+ self.assertEqual(42, parent.session_id)
+
+ def test_join(self):
+ P = self.cls
+ sep = self.cls.parser.sep
+ p = P(f'a{sep}b')
+ pp = p.joinpath('c')
+ self.assertEqual(pp, P(f'a{sep}b{sep}c'))
+ self.assertIs(type(pp), type(p))
+ pp = p.joinpath('c', 'd')
+ self.assertEqual(pp, P(f'a{sep}b{sep}c{sep}d'))
+ pp = p.joinpath(f'{sep}c')
+ self.assertEqual(pp, P(f'{sep}c'))
+
+ def test_div(self):
+ # Basically the same as joinpath().
+ P = self.cls
+ sep = self.cls.parser.sep
+ p = P(f'a{sep}b')
+ pp = p / 'c'
+ self.assertEqual(pp, P(f'a{sep}b{sep}c'))
+ self.assertIs(type(pp), type(p))
+ pp = p / f'c{sep}d'
+ self.assertEqual(pp, P(f'a{sep}b{sep}c{sep}d'))
+ pp = p / 'c' / 'd'
+ self.assertEqual(pp, P(f'a{sep}b{sep}c{sep}d'))
+ pp = 'c' / p / 'd'
+ self.assertEqual(pp, P(f'c{sep}a{sep}b{sep}d'))
+ pp = p/ f'{sep}c'
+ self.assertEqual(pp, P(f'{sep}c'))
+
+ def test_full_match(self):
+ P = self.cls
+ # Simple relative pattern.
+ self.assertTrue(P('b.py').full_match('b.py'))
+ self.assertFalse(P('a/b.py').full_match('b.py'))
+ self.assertFalse(P('/a/b.py').full_match('b.py'))
+ self.assertFalse(P('a.py').full_match('b.py'))
+ self.assertFalse(P('b/py').full_match('b.py'))
+ self.assertFalse(P('/a.py').full_match('b.py'))
+ self.assertFalse(P('b.py/c').full_match('b.py'))
+ # Wildcard relative pattern.
+ self.assertTrue(P('b.py').full_match('*.py'))
+ self.assertFalse(P('a/b.py').full_match('*.py'))
+ self.assertFalse(P('/a/b.py').full_match('*.py'))
+ self.assertFalse(P('b.pyc').full_match('*.py'))
+ self.assertFalse(P('b./py').full_match('*.py'))
+ self.assertFalse(P('b.py/c').full_match('*.py'))
+ # Multi-part relative pattern.
+ self.assertTrue(P('ab/c.py').full_match('a*/*.py'))
+ self.assertFalse(P('/d/ab/c.py').full_match('a*/*.py'))
+ self.assertFalse(P('a.py').full_match('a*/*.py'))
+ self.assertFalse(P('/dab/c.py').full_match('a*/*.py'))
+ self.assertFalse(P('ab/c.py/d').full_match('a*/*.py'))
+ # Absolute pattern.
+ self.assertTrue(P('/b.py').full_match('/*.py'))
+ self.assertFalse(P('b.py').full_match('/*.py'))
+ self.assertFalse(P('a/b.py').full_match('/*.py'))
+ self.assertFalse(P('/a/b.py').full_match('/*.py'))
+ # Multi-part absolute pattern.
+ self.assertTrue(P('/a/b.py').full_match('/a/*.py'))
+ self.assertFalse(P('/ab.py').full_match('/a/*.py'))
+ self.assertFalse(P('/a/b/c.py').full_match('/a/*.py'))
+ # Multi-part glob-style pattern.
+ self.assertTrue(P('a').full_match('**'))
+ self.assertTrue(P('c.py').full_match('**'))
+ self.assertTrue(P('a/b/c.py').full_match('**'))
+ self.assertTrue(P('/a/b/c.py').full_match('**'))
+ self.assertTrue(P('/a/b/c.py').full_match('/**'))
+ self.assertTrue(P('/a/b/c.py').full_match('/a/**'))
+ self.assertTrue(P('/a/b/c.py').full_match('**/*.py'))
+ self.assertTrue(P('/a/b/c.py').full_match('/**/*.py'))
+ self.assertTrue(P('/a/b/c.py').full_match('/a/**/*.py'))
+ self.assertTrue(P('/a/b/c.py').full_match('/a/b/**/*.py'))
+ self.assertTrue(P('/a/b/c.py').full_match('/**/**/**/**/*.py'))
+ self.assertFalse(P('c.py').full_match('**/a.py'))
+ self.assertFalse(P('c.py').full_match('c/**'))
+ self.assertFalse(P('a/b/c.py').full_match('**/a'))
+ self.assertFalse(P('a/b/c.py').full_match('**/a/b'))
+ self.assertFalse(P('a/b/c.py').full_match('**/a/b/c'))
+ self.assertFalse(P('a/b/c.py').full_match('**/a/b/c.'))
+ self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**'))
+ self.assertFalse(P('a/b/c.py').full_match('**/a/b/c./**'))
+ self.assertFalse(P('a/b/c.py').full_match('/a/b/c.py/**'))
+ self.assertFalse(P('a/b/c.py').full_match('/**/a/b/c.py'))
+ # Case-sensitive flag
+ self.assertFalse(P('A.py').full_match('a.PY', case_sensitive=True))
+ self.assertTrue(P('A.py').full_match('a.PY', case_sensitive=False))
+ self.assertFalse(P('c:/a/B.Py').full_match('C:/A/*.pY', case_sensitive=True))
+ self.assertTrue(P('/a/b/c.py').full_match('/A/*/*.Py', case_sensitive=False))
+ # Matching against empty path
+ self.assertFalse(P('').full_match('*'))
+ self.assertTrue(P('').full_match('**'))
+ self.assertFalse(P('').full_match('**/*'))
+ # Matching with empty pattern
+ self.assertTrue(P('').full_match(''))
+ self.assertTrue(P('.').full_match('.'))
+ self.assertFalse(P('/').full_match(''))
+ self.assertFalse(P('/').full_match('.'))
+ self.assertFalse(P('foo').full_match(''))
+ self.assertFalse(P('foo').full_match('.'))
+
+ def test_parts(self):
+ # `parts` returns a tuple.
+ sep = self.cls.parser.sep
+ P = self.cls
+ p = P(f'a{sep}b')
+ parts = p.parts
+ self.assertEqual(parts, ('a', 'b'))
+ # When the path is absolute, the anchor is a separate part.
+ p = P(f'{sep}a{sep}b')
+ parts = p.parts
+ self.assertEqual(parts, (sep, 'a', 'b'))
+
+ def test_parent(self):
+ # Relative
+ P = self.cls
+ 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(''))
+ # Anchored
+ 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(self):
+ # Relative
+ P = self.cls
+ 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]
+ with self.assertRaises(TypeError):
+ par[0] = p
+ # Anchored
+ 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_anchor(self):
+ P = self.cls
+ sep = self.cls.parser.sep
+ self.assertEqual(P('').anchor, '')
+ self.assertEqual(P(f'a{sep}b').anchor, '')
+ self.assertEqual(P(sep).anchor, sep)
+ self.assertEqual(P(f'{sep}a{sep}b').anchor, sep)
+
+ def test_name(self):
+ P = self.cls
+ 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.py').name, 'b.py')
+ self.assertEqual(P('/a/b.py').name, 'b.py')
+
+ def test_suffix(self):
+ P = self.cls
+ self.assertEqual(P('').suffix, '')
+ self.assertEqual(P('.').suffix, '')
+ 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.py').suffix, '.py')
+ self.assertEqual(P('/a/b.py').suffix, '.py')
+ self.assertEqual(P('a/.hgrc').suffix, '')
+ self.assertEqual(P('/a/.hgrc').suffix, '')
+ self.assertEqual(P('a/.hg.rc').suffix, '.rc')
+ self.assertEqual(P('/a/.hg.rc').suffix, '.rc')
+ self.assertEqual(P('a/b.tar.gz').suffix, '.gz')
+ self.assertEqual(P('/a/b.tar.gz').suffix, '.gz')
+ self.assertEqual(P('a/trailing.dot.').suffix, '.')
+ self.assertEqual(P('/a/trailing.dot.').suffix, '.')
+ self.assertEqual(P('a/..d.o.t..').suffix, '.')
+ self.assertEqual(P('a/inn.er..dots').suffix, '.dots')
+ self.assertEqual(P('photo').suffix, '')
+ self.assertEqual(P('photo.jpg').suffix, '.jpg')
+
+ def test_suffixes(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.py').suffixes, ['.py'])
+ self.assertEqual(P('/a/b.py').suffixes, ['.py'])
+ self.assertEqual(P('a/.hgrc').suffixes, [])
+ self.assertEqual(P('/a/.hgrc').suffixes, [])
+ self.assertEqual(P('a/.hg.rc').suffixes, ['.rc'])
+ self.assertEqual(P('/a/.hg.rc').suffixes, ['.rc'])
+ self.assertEqual(P('a/b.tar.gz').suffixes, ['.tar', '.gz'])
+ self.assertEqual(P('/a/b.tar.gz').suffixes, ['.tar', '.gz'])
+ self.assertEqual(P('a/trailing.dot.').suffixes, ['.dot', '.'])
+ self.assertEqual(P('/a/trailing.dot.').suffixes, ['.dot', '.'])
+ self.assertEqual(P('a/..d.o.t..').suffixes, ['.o', '.t', '.', '.'])
+ self.assertEqual(P('a/inn.er..dots').suffixes, ['.er', '.', '.dots'])
+ self.assertEqual(P('photo').suffixes, [])
+ self.assertEqual(P('photo.jpg').suffixes, ['.jpg'])
+
+ def test_stem(self):
+ P = self.cls
+ self.assertEqual(P('..').stem, '..')
+ self.assertEqual(P('').stem, '')
+ self.assertEqual(P('/').stem, '')
+ 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/trailing.dot.').stem, 'trailing.dot')
+ self.assertEqual(P('a/..d.o.t..').stem, '..d.o.t.')
+ self.assertEqual(P('a/inn.er..dots').stem, 'inn.er.')
+ self.assertEqual(P('photo').stem, 'photo')
+ self.assertEqual(P('photo.jpg').stem, 'photo')
+
+ def test_with_name(self):
+ P = self.cls
+ self.assertEqual(P('a/b').with_name('d.xml'), P('a/d.xml'))
+ self.assertEqual(P('/a/b').with_name('d.xml'), P('/a/d.xml'))
+ self.assertEqual(P('a/b.py').with_name('d.xml'), P('a/d.xml'))
+ 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.assertRaises(ValueError, P('a/b').with_name, '/c')
+ self.assertRaises(ValueError, P('a/b').with_name, 'c/')
+ self.assertRaises(ValueError, P('a/b').with_name, 'c/d')
+
+ def test_with_stem(self):
+ P = self.cls
+ self.assertEqual(P('a/b').with_stem('d'), P('a/d'))
+ self.assertEqual(P('/a/b').with_stem('d'), P('/a/d'))
+ self.assertEqual(P('a/b.py').with_stem('d'), P('a/d.py'))
+ self.assertEqual(P('/a/b.py').with_stem('d'), P('/a/d.py'))
+ 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.assertRaises(ValueError, P('foo.gz').with_stem, '')
+ self.assertRaises(ValueError, P('/a/b/foo.gz').with_stem, '')
+ self.assertRaises(ValueError, P('a/b').with_stem, '/c')
+ self.assertRaises(ValueError, P('a/b').with_stem, 'c/')
+ self.assertRaises(ValueError, P('a/b').with_stem, 'c/d')
+
+ def test_with_suffix(self):
+ P = self.cls
+ self.assertEqual(P('a/b').with_suffix('.gz'), P('a/b.gz'))
+ 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'))
+ # Stripping suffix.
+ self.assertEqual(P('a/b.py').with_suffix(''), P('a/b'))
+ self.assertEqual(P('/a/b').with_suffix(''), P('/a/b'))
+ # Single dot
+ self.assertEqual(P('a/b').with_suffix('.'), P('a/b.'))
+ self.assertEqual(P('/a/b').with_suffix('.'), P('/a/b.'))
+ self.assertEqual(P('a/b.py').with_suffix('.'), P('a/b.'))
+ self.assertEqual(P('/a/b.py').with_suffix('.'), P('/a/b.'))
+ # Path doesn't have a "filename" component.
+ self.assertRaises(ValueError, P('').with_suffix, '.gz')
+ self.assertRaises(ValueError, P('/').with_suffix, '.gz')
+ # Invalid suffix.
+ self.assertRaises(ValueError, P('a/b').with_suffix, 'gz')
+ self.assertRaises(ValueError, P('a/b').with_suffix, '/')
+ self.assertRaises(ValueError, P('a/b').with_suffix, '/.gz')
+ self.assertRaises(ValueError, P('a/b').with_suffix, 'c/d')
+ self.assertRaises(ValueError, P('a/b').with_suffix, '.c/.d')
+ self.assertRaises(ValueError, P('a/b').with_suffix, './.d')
+ self.assertRaises(ValueError, P('a/b').with_suffix, '.d/.')
+ self.assertRaises(TypeError, P('a/b').with_suffix, None)
+
+
+class LexicalPathJoinTest(JoinTestBase, unittest.TestCase):
+ cls = LexicalPath
+
+
+class PurePathJoinTest(JoinTestBase, unittest.TestCase):
+ cls = PurePath
+
+
+class PathJoinTest(JoinTestBase, unittest.TestCase):
+ cls = Path
+
+
+if __name__ == "__main__":
+ unittest.main()