https://github.com/python/cpython/commit/db6a998b18e9476226507144b3b2fab854095dbc commit: db6a998b18e9476226507144b3b2fab854095dbc branch: main author: Barney Gale <barney.g...@gmail.com> committer: barneygale <barney.g...@gmail.com> date: 2025-03-12T19:06:43Z summary:
GH-130614: pathlib ABCs: revise test suite for writable paths (#131112) Test `pathlib.types._WritablePath` in a dedicated test module. These tests cover `WritableZipPath`, `WritableLocalPath` and `Path`, where the former two classes are implementations of `_WritablePath` for use in tests. files: A Lib/test/test_pathlib/test_write.py M Lib/test/test_pathlib/support/local_path.py M Lib/test/test_pathlib/support/zip_path.py M Lib/test/test_pathlib/test_pathlib_abc.py diff --git a/Lib/test/test_pathlib/support/local_path.py b/Lib/test/test_pathlib/support/local_path.py index 1d2b03df225978..1cf64316b40ca7 100644 --- a/Lib/test/test_pathlib/support/local_path.py +++ b/Lib/test/test_pathlib/support/local_path.py @@ -1,5 +1,6 @@ """ -Implementation of ReadablePath for local paths, for use in pathlib tests. +Implementations of ReadablePath and WritablePath for local paths, for use in +pathlib tests. LocalPathGround is also defined here. It helps establish the "ground truth" about local paths in tests. @@ -143,3 +144,23 @@ def iterdir(self): def readlink(self): return self.with_segments(os.readlink(self)) + + +class WritableLocalPath(pathlib.types._WritablePath, LexicalPath): + """ + Simple implementation of a WritablePath class for local filesystem paths. + """ + + __slots__ = () + + def __fspath__(self): + return str(self) + + def __open_wb__(self, buffering=-1): + return open(self, 'wb') + + def mkdir(self, mode=0o777): + os.mkdir(self, mode) + + def symlink_to(self, target, target_is_directory=False): + os.symlink(target, self, target_is_directory) diff --git a/Lib/test/test_pathlib/support/zip_path.py b/Lib/test/test_pathlib/support/zip_path.py index ab6a929fc4a504..4e24e35a03afb7 100644 --- a/Lib/test/test_pathlib/support/zip_path.py +++ b/Lib/test/test_pathlib/support/zip_path.py @@ -1,5 +1,6 @@ """ -Implementation of ReadablePath for zip file members, for use in pathlib tests. +Implementations of ReadablePath and WritablePath for zip file members, for use +in pathlib tests. ZipPathGround is also defined here. It helps establish the "ground truth" about zip file members in tests. @@ -276,3 +277,48 @@ def readlink(self): elif not info.is_symlink(): raise OSError(errno.EINVAL, "Not a symlink", self) return self.with_segments(self.zip_file.read(info.zip_info).decode()) + + +class WritableZipPath(pathlib.types._WritablePath): + """ + Simple implementation of a WritablePath class for .zip files. + """ + + __slots__ = ('_segments', 'zip_file') + parser = posixpath + + def __init__(self, *pathsegments, zip_file): + self._segments = pathsegments + self.zip_file = zip_file + + def __hash__(self): + return hash((str(self), self.zip_file)) + + def __eq__(self, other): + if not isinstance(other, WritableZipPath): + return NotImplemented + return str(self) == str(other) and self.zip_file is other.zip_file + + def __str__(self): + if not self._segments: + return '' + return self.parser.join(*self._segments) + + def __repr__(self): + return f'{type(self).__name__}({str(self)!r}, zip_file={self.zip_file!r})' + + def with_segments(self, *pathsegments): + return type(self)(*pathsegments, zip_file=self.zip_file) + + def __open_wb__(self, buffering=-1): + return self.zip_file.open(str(self), 'w') + + def mkdir(self, mode=0o777): + self.zip_file.mkdir(str(self), mode) + + def symlink_to(self, target, target_is_directory=False): + zinfo = zipfile.ZipInfo(str(self))._for_archive(self.zip_file) + zinfo.external_attr = stat.S_IFLNK << 16 + if target_is_directory: + zinfo.external_attr |= 0x10 + self.zip_file.writestr(zinfo, str(target)) diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index 02e2a1da7ee7d2..cff4e3372b7a3b 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -336,10 +336,6 @@ def test_glob_windows(self): class WritablePathTest(JoinablePathTest): cls = DummyWritablePath - def test_is_writable(self): - p = self.cls(self.base) - self.assertIsInstance(p, _WritablePath) - class DummyRWPath(DummyWritablePath, DummyReadablePath): __slots__ = () @@ -349,43 +345,6 @@ class RWPathTest(WritablePathTest, ReadablePathTest): cls = DummyRWPath can_symlink = False - def test_read_write_bytes(self): - p = self.cls(self.base) - (p / 'fileA').write_bytes(b'abcdefg') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - # Check that trying to write str does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_bytes, 'somestr') - self.assertEqual((p / 'fileA').read_bytes(), b'abcdefg') - - def test_read_write_text(self): - p = self.cls(self.base) - (p / 'fileA').write_text('äbcdefg', encoding='latin-1') - self.assertEqual((p / 'fileA').read_text( - encoding='utf-8', errors='ignore'), 'bcdefg') - # Check that trying to write bytes does not truncate the file. - self.assertRaises(TypeError, (p / 'fileA').write_text, b'somebytes') - self.assertEqual((p / 'fileA').read_text(encoding='latin-1'), 'äbcdefg') - - def test_write_text_with_newlines(self): - p = self.cls(self.base) - # Check that `\n` character change nothing - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\nfghlk\n\rmnopq') - # Check that `\r` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\rfghlk\r\rmnopq') - # Check that `\r\n` character replaces `\n` - (p / 'fileA').write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde\r\r\nfghlk\r\n\rmnopq') - # Check that no argument passed will change `\n` to `os.linesep` - os_linesep_byte = bytes(os.linesep, encoding='ascii') - (p / 'fileA').write_text('abcde\nfghlk\n\rmnopq') - self.assertEqual((p / 'fileA').read_bytes(), - b'abcde' + os_linesep_byte + b'fghlk' + os_linesep_byte + b'\rmnopq') - def test_copy_file(self): base = self.cls(self.base) source = base / 'fileA' diff --git a/Lib/test/test_pathlib/test_write.py b/Lib/test/test_pathlib/test_write.py new file mode 100644 index 00000000000000..3d6057fb9dd4a4 --- /dev/null +++ b/Lib/test/test_pathlib/test_write.py @@ -0,0 +1,109 @@ +""" +Tests for pathlib.types._WritablePath +""" + +import io +import os +import unittest + +from pathlib import Path +from pathlib.types import _WritablePath +from pathlib._os import magic_open + +from test.test_pathlib.support.local_path import WritableLocalPath, LocalPathGround +from test.test_pathlib.support.zip_path import WritableZipPath, ZipPathGround + + +class WriteTestBase: + def setUp(self): + self.root = self.ground.setup() + + def tearDown(self): + self.ground.teardown(self.root) + + def test_is_writable(self): + self.assertIsInstance(self.root, _WritablePath) + + def test_open_w(self): + p = self.root / 'fileA' + with magic_open(p, 'w') as f: + self.assertIsInstance(f, io.TextIOBase) + f.write('this is file A\n') + self.assertEqual(self.ground.readtext(p), 'this is file A\n') + + def test_open_wb(self): + p = self.root / 'fileA' + with magic_open(p, 'wb') as f: + #self.assertIsInstance(f, io.BufferedWriter) + f.write(b'this is file A\n') + self.assertEqual(self.ground.readbytes(p), b'this is file A\n') + + def test_write_bytes(self): + p = self.root / 'fileA' + p.write_bytes(b'abcdefg') + self.assertEqual(self.ground.readbytes(p), b'abcdefg') + # Check that trying to write str does not truncate the file. + self.assertRaises(TypeError, p.write_bytes, 'somestr') + self.assertEqual(self.ground.readbytes(p), b'abcdefg') + + def test_write_text(self): + p = self.root / 'fileA' + p.write_text('äbcdefg', encoding='latin-1') + self.assertEqual(self.ground.readbytes(p), b'\xe4bcdefg') + # Check that trying to write bytes does not truncate the file. + self.assertRaises(TypeError, p.write_text, b'somebytes') + self.assertEqual(self.ground.readbytes(p), b'\xe4bcdefg') + + def test_write_text_with_newlines(self): + # Check that `\n` character change nothing + p = self.root / 'fileA' + p.write_text('abcde\r\nfghlk\n\rmnopq', newline='\n') + self.assertEqual(self.ground.readbytes(p), b'abcde\r\nfghlk\n\rmnopq') + + # Check that `\r` character replaces `\n` + p = self.root / 'fileB' + p.write_text('abcde\r\nfghlk\n\rmnopq', newline='\r') + self.assertEqual(self.ground.readbytes(p), b'abcde\r\rfghlk\r\rmnopq') + + # Check that `\r\n` character replaces `\n` + p = self.root / 'fileC' + p.write_text('abcde\r\nfghlk\n\rmnopq', newline='\r\n') + self.assertEqual(self.ground.readbytes(p), b'abcde\r\r\nfghlk\r\n\rmnopq') + + # Check that no argument passed will change `\n` to `os.linesep` + os_linesep_byte = bytes(os.linesep, encoding='ascii') + p = self.root / 'fileD' + p.write_text('abcde\nfghlk\n\rmnopq') + self.assertEqual(self.ground.readbytes(p), + b'abcde' + os_linesep_byte + + b'fghlk' + os_linesep_byte + b'\rmnopq') + + def test_mkdir(self): + p = self.root / 'newdirA' + self.assertFalse(self.ground.isdir(p)) + p.mkdir() + self.assertTrue(self.ground.isdir(p)) + + def test_symlink_to(self): + if not self.ground.can_symlink: + self.skipTest('needs symlinks') + link = self.root.joinpath('linkA') + link.symlink_to('fileA') + self.assertTrue(self.ground.islink(link)) + self.assertEqual(self.ground.readlink(link), 'fileA') + + +class ZipPathWriteTest(WriteTestBase, unittest.TestCase): + ground = ZipPathGround(WritableZipPath) + + +class LocalPathWriteTest(WriteTestBase, unittest.TestCase): + ground = LocalPathGround(WritableLocalPath) + + +class PathWriteTest(WriteTestBase, unittest.TestCase): + ground = LocalPathGround(Path) + + +if __name__ == "__main__": + unittest.main() _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com