GH-130614: pathlib ABCs: revise test suite for readable paths (#131018)

Test `pathlib.types._ReadablePath` in a dedicated test module. These tests
cover `ReadableZipPath`, `ReadableLocalPath` and `Path`, where the former
two classes are implementations of `_ReadablePath` for use in tests.
This commit is contained in:
Barney Gale 2025-03-11 20:54:22 +00:00 committed by GitHub
parent 24070492cf
commit ad90c5fabc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 759 additions and 268 deletions

View file

@ -314,76 +314,6 @@ class ReadablePathTest(JoinablePathTest):
normcase = self.parser.normcase
self.assertEqual(normcase(path_a), normcase(path_b))
def test_is_readable(self):
p = self.cls(self.base)
self.assertIsInstance(p, _ReadablePath)
def test_magic_open(self):
p = self.cls(self.base)
with magic_open(p / 'fileA', 'r') as f:
self.assertIsInstance(f, io.TextIOBase)
self.assertEqual(f.read(), "this is file A\n")
with magic_open(p / 'fileA', 'rb') as f:
self.assertIsInstance(f, io.BufferedIOBase)
self.assertEqual(f.read().strip(), b"this is file A")
def test_iterdir(self):
P = self.cls
p = P(self.base)
it = p.iterdir()
paths = set(it)
expected = ['dirA', 'dirB', 'dirC', 'dirE', 'fileA']
if self.can_symlink:
expected += ['linkA', 'linkB', 'brokenLink', 'brokenLinkLoop']
self.assertEqual(paths, { P(self.base, q) for q in expected })
def test_iterdir_nodir(self):
# __iter__ on something that is not a directory.
p = self.cls(self.base, 'fileA')
with self.assertRaises(OSError) as cm:
p.iterdir()
# ENOENT or EINVAL under Windows, ENOTDIR otherwise
# (see issue #12802).
self.assertIn(cm.exception.errno, (errno.ENOTDIR,
errno.ENOENT, errno.EINVAL))
def test_iterdir_info(self):
p = self.cls(self.base)
for child in p.iterdir():
self.assertIsInstance(child.info, PathInfo)
self.assertTrue(child.info.exists(follow_symlinks=False))
def test_glob_common(self):
def _check(glob, expected):
self.assertEqual(set(glob), { P(self.base, q) for q in expected })
P = self.cls
p = P(self.base)
it = p.glob("fileA")
self.assertIsInstance(it, collections.abc.Iterator)
_check(it, ["fileA"])
_check(p.glob("fileB"), [])
_check(p.glob("dir*/file*"), ["dirB/fileB", "dirC/fileC"])
if not self.can_symlink:
_check(p.glob("*A"), ['dirA', 'fileA'])
else:
_check(p.glob("*A"), ['dirA', 'fileA', 'linkA'])
if not self.can_symlink:
_check(p.glob("*B/*"), ['dirB/fileB'])
else:
_check(p.glob("*B/*"), ['dirB/fileB', 'dirB/linkD',
'linkB/fileB', 'linkB/linkD'])
if not self.can_symlink:
_check(p.glob("*/fileB"), ['dirB/fileB'])
else:
_check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
if self.can_symlink:
_check(p.glob("brokenLink"), ['brokenLink'])
if not self.can_symlink:
_check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/"])
else:
_check(p.glob("*/"), ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
@needs_posix
def test_glob_posix(self):
P = self.cls
@ -402,123 +332,6 @@ class ReadablePathTest(JoinablePathTest):
self.assertEqual(set(p.glob("*a\\")), { P(self.base, "dirA/") })
self.assertEqual(set(p.glob("F*a")), { P(self.base, "fileA") })
def test_glob_empty_pattern(self):
P = self.cls
p = P(self.base)
self.assertEqual(list(p.glob("")), [p.joinpath("")])
def test_info_exists(self):
p = self.cls(self.base)
self.assertTrue(p.info.exists())
self.assertTrue((p / 'dirA').info.exists())
self.assertTrue((p / 'dirA').info.exists(follow_symlinks=False))
self.assertTrue((p / 'fileA').info.exists())
self.assertTrue((p / 'fileA').info.exists(follow_symlinks=False))
self.assertFalse((p / 'non-existing').info.exists())
self.assertFalse((p / 'non-existing').info.exists(follow_symlinks=False))
if self.can_symlink:
self.assertTrue((p / 'linkA').info.exists())
self.assertTrue((p / 'linkA').info.exists(follow_symlinks=False))
self.assertTrue((p / 'linkB').info.exists())
self.assertTrue((p / 'linkB').info.exists(follow_symlinks=True))
self.assertFalse((p / 'brokenLink').info.exists())
self.assertTrue((p / 'brokenLink').info.exists(follow_symlinks=False))
self.assertFalse((p / 'brokenLinkLoop').info.exists())
self.assertTrue((p / 'brokenLinkLoop').info.exists(follow_symlinks=False))
self.assertFalse((p / 'fileA\udfff').info.exists())
self.assertFalse((p / 'fileA\udfff').info.exists(follow_symlinks=False))
self.assertFalse((p / 'fileA\x00').info.exists())
self.assertFalse((p / 'fileA\x00').info.exists(follow_symlinks=False))
def test_info_exists_caching(self):
p = self.cls(self.base)
q = p / 'myfile'
self.assertFalse(q.info.exists())
self.assertFalse(q.info.exists(follow_symlinks=False))
if isinstance(self.cls, _WritablePath):
q.write_text('hullo')
self.assertFalse(q.info.exists())
self.assertFalse(q.info.exists(follow_symlinks=False))
def test_info_is_dir(self):
p = self.cls(self.base)
self.assertTrue((p / 'dirA').info.is_dir())
self.assertTrue((p / 'dirA').info.is_dir(follow_symlinks=False))
self.assertFalse((p / 'fileA').info.is_dir())
self.assertFalse((p / 'fileA').info.is_dir(follow_symlinks=False))
self.assertFalse((p / 'non-existing').info.is_dir())
self.assertFalse((p / 'non-existing').info.is_dir(follow_symlinks=False))
if self.can_symlink:
self.assertFalse((p / 'linkA').info.is_dir())
self.assertFalse((p / 'linkA').info.is_dir(follow_symlinks=False))
self.assertTrue((p / 'linkB').info.is_dir())
self.assertFalse((p / 'linkB').info.is_dir(follow_symlinks=False))
self.assertFalse((p / 'brokenLink').info.is_dir())
self.assertFalse((p / 'brokenLink').info.is_dir(follow_symlinks=False))
self.assertFalse((p / 'brokenLinkLoop').info.is_dir())
self.assertFalse((p / 'brokenLinkLoop').info.is_dir(follow_symlinks=False))
self.assertFalse((p / 'dirA\udfff').info.is_dir())
self.assertFalse((p / 'dirA\udfff').info.is_dir(follow_symlinks=False))
self.assertFalse((p / 'dirA\x00').info.is_dir())
self.assertFalse((p / 'dirA\x00').info.is_dir(follow_symlinks=False))
def test_info_is_dir_caching(self):
p = self.cls(self.base)
q = p / 'mydir'
self.assertFalse(q.info.is_dir())
self.assertFalse(q.info.is_dir(follow_symlinks=False))
if isinstance(self.cls, _WritablePath):
q.mkdir()
self.assertFalse(q.info.is_dir())
self.assertFalse(q.info.is_dir(follow_symlinks=False))
def test_info_is_file(self):
p = self.cls(self.base)
self.assertTrue((p / 'fileA').info.is_file())
self.assertTrue((p / 'fileA').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'dirA').info.is_file())
self.assertFalse((p / 'dirA').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'non-existing').info.is_file())
self.assertFalse((p / 'non-existing').info.is_file(follow_symlinks=False))
if self.can_symlink:
self.assertTrue((p / 'linkA').info.is_file())
self.assertFalse((p / 'linkA').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'linkB').info.is_file())
self.assertFalse((p / 'linkB').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'brokenLink').info.is_file())
self.assertFalse((p / 'brokenLink').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'brokenLinkLoop').info.is_file())
self.assertFalse((p / 'brokenLinkLoop').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'fileA\udfff').info.is_file())
self.assertFalse((p / 'fileA\udfff').info.is_file(follow_symlinks=False))
self.assertFalse((p / 'fileA\x00').info.is_file())
self.assertFalse((p / 'fileA\x00').info.is_file(follow_symlinks=False))
def test_info_is_file_caching(self):
p = self.cls(self.base)
q = p / 'myfile'
self.assertFalse(q.info.is_file())
self.assertFalse(q.info.is_file(follow_symlinks=False))
if isinstance(self.cls, _WritablePath):
q.write_text('hullo')
self.assertFalse(q.info.is_file())
self.assertFalse(q.info.is_file(follow_symlinks=False))
def test_info_is_symlink(self):
p = self.cls(self.base)
self.assertFalse((p / 'fileA').info.is_symlink())
self.assertFalse((p / 'dirA').info.is_symlink())
self.assertFalse((p / 'non-existing').info.is_symlink())
if self.can_symlink:
self.assertTrue((p / 'linkA').info.is_symlink())
self.assertTrue((p / 'linkB').info.is_symlink())
self.assertTrue((p / 'brokenLink').info.is_symlink())
self.assertFalse((p / 'linkA\udfff').info.is_symlink())
self.assertFalse((p / 'linkA\x00').info.is_symlink())
self.assertTrue((p / 'brokenLinkLoop').info.is_symlink())
self.assertFalse((p / 'fileA\udfff').info.is_symlink())
self.assertFalse((p / 'fileA\x00').info.is_symlink())
class WritablePathTest(JoinablePathTest):
cls = DummyWritablePath
@ -553,21 +366,6 @@ class RWPathTest(WritablePathTest, ReadablePathTest):
self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes')
self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg')
def test_read_text_with_newlines(self):
p = self.cls(self.base)
# Check that `\n` character change nothing
(p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
self.assertEqual((p / 'fileA').read_text(newline='\n'),
'abcde\r\nfghlk\n\rmnopq')
# Check that `\r` character replaces `\n`
(p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
self.assertEqual((p / 'fileA').read_text(newline='\r'),
'abcde\r\nfghlk\n\rmnopq')
# Check that `\r\n` character replaces `\n`
(p / 'fileA').write_bytes(b'abcde\r\nfghlk\n\rmnopq')
self.assertEqual((p / 'fileA').read_text(newline='\r\n'),
'abcde\r\nfghlk\n\rmnopq')
def test_write_text_with_newlines(self):
p = self.cls(self.base)
# Check that `\n` character change nothing
@ -763,72 +561,6 @@ class ReadablePathWalkTest(unittest.TestCase):
cls._files.clear()
cls._directories.clear()
def test_walk_topdown(self):
walker = self.walk_path.walk()
entry = next(walker)
entry[1].sort() # Ensure we visit SUB1 before SUB2
self.assertEqual(entry, (self.walk_path, ["SUB1", "SUB2"], ["tmp1"]))
entry = next(walker)
self.assertEqual(entry, (self.sub1_path, ["SUB11"], ["tmp2"]))
entry = next(walker)
self.assertEqual(entry, (self.sub11_path, [], []))
entry = next(walker)
entry[1].sort()
entry[2].sort()
self.assertEqual(entry, self.sub2_tree)
with self.assertRaises(StopIteration):
next(walker)
def test_walk_prune(self):
# Prune the search.
all = []
for root, dirs, files in self.walk_path.walk():
all.append((root, dirs, files))
if 'SUB1' in dirs:
# Note that this also mutates the dirs we appended to all!
dirs.remove('SUB1')
self.assertEqual(len(all), 2)
self.assertEqual(all[0], (self.walk_path, ["SUB2"], ["tmp1"]))
all[1][-1].sort()
all[1][1].sort()
self.assertEqual(all[1], self.sub2_tree)
def test_walk_bottom_up(self):
seen_testfn = seen_sub1 = seen_sub11 = seen_sub2 = False
for path, dirnames, filenames in self.walk_path.walk(top_down=False):
if path == self.walk_path:
self.assertFalse(seen_testfn)
self.assertTrue(seen_sub1)
self.assertTrue(seen_sub2)
self.assertEqual(sorted(dirnames), ["SUB1", "SUB2"])
self.assertEqual(filenames, ["tmp1"])
seen_testfn = True
elif path == self.sub1_path:
self.assertFalse(seen_testfn)
self.assertFalse(seen_sub1)
self.assertTrue(seen_sub11)
self.assertEqual(dirnames, ["SUB11"])
self.assertEqual(filenames, ["tmp2"])
seen_sub1 = True
elif path == self.sub11_path:
self.assertFalse(seen_sub1)
self.assertFalse(seen_sub11)
self.assertEqual(dirnames, [])
self.assertEqual(filenames, [])
seen_sub11 = True
elif path == self.sub2_path:
self.assertFalse(seen_testfn)
self.assertFalse(seen_sub2)
self.assertEqual(sorted(dirnames), sorted(self.sub2_tree[1]))
self.assertEqual(sorted(filenames), sorted(self.sub2_tree[2]))
seen_sub2 = True
else:
raise AssertionError(f"Unexpected path: {path}")
self.assertTrue(seen_testfn)
if __name__ == "__main__":
unittest.main()