mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	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.
This commit is contained in:
		
							parent
							
								
									ea57ffa02e
								
							
						
					
					
						commit
						db6a998b18
					
				
					 4 changed files with 178 additions and 43 deletions
				
			
		|  | @ -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" | LocalPathGround is also defined here. It helps establish the "ground truth" | ||||||
| about local paths in tests. | about local paths in tests. | ||||||
|  | @ -143,3 +144,23 @@ def iterdir(self): | ||||||
| 
 | 
 | ||||||
|     def readlink(self): |     def readlink(self): | ||||||
|         return self.with_segments(os.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) | ||||||
|  |  | ||||||
|  | @ -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" | ZipPathGround is also defined here. It helps establish the "ground truth" | ||||||
| about zip file members in tests. | about zip file members in tests. | ||||||
|  | @ -276,3 +277,48 @@ def readlink(self): | ||||||
|         elif not info.is_symlink(): |         elif not info.is_symlink(): | ||||||
|             raise OSError(errno.EINVAL, "Not a symlink", self) |             raise OSError(errno.EINVAL, "Not a symlink", self) | ||||||
|         return self.with_segments(self.zip_file.read(info.zip_info).decode()) |         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)) | ||||||
|  |  | ||||||
|  | @ -336,10 +336,6 @@ def test_glob_windows(self): | ||||||
| class WritablePathTest(JoinablePathTest): | class WritablePathTest(JoinablePathTest): | ||||||
|     cls = DummyWritablePath |     cls = DummyWritablePath | ||||||
| 
 | 
 | ||||||
|     def test_is_writable(self): |  | ||||||
|         p = self.cls(self.base) |  | ||||||
|         self.assertIsInstance(p, _WritablePath) |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class DummyRWPath(DummyWritablePath, DummyReadablePath): | class DummyRWPath(DummyWritablePath, DummyReadablePath): | ||||||
|     __slots__ = () |     __slots__ = () | ||||||
|  | @ -349,43 +345,6 @@ class RWPathTest(WritablePathTest, ReadablePathTest): | ||||||
|     cls = DummyRWPath |     cls = DummyRWPath | ||||||
|     can_symlink = False |     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): |     def test_copy_file(self): | ||||||
|         base = self.cls(self.base) |         base = self.cls(self.base) | ||||||
|         source = base / 'fileA' |         source = base / 'fileA' | ||||||
|  |  | ||||||
							
								
								
									
										109
									
								
								Lib/test/test_pathlib/test_write.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								Lib/test/test_pathlib/test_write.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -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() | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Barney Gale
						Barney Gale