mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +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 @@ class ReadableLocalPath(pathlib.types._ReadablePath, LexicalPath):
|
||||||
|
|
||||||
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 @@ class ReadableZipPath(pathlib.types._ReadablePath):
|
||||||
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 @@ class ReadablePathTest(JoinablePathTest):
|
||||||
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