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

Reply via email to