Update io from v3.14.3

This commit is contained in:
CPython Developers authored and Jeong, YunWon committed 2026-02-13 14:13:30 +09:00
1 parent 44d9c70d18
commit ae9caada50
6 files changed
+547 -239

No files matched your search

+119 -67
View File
@@ -16,15 +16,16 @@ else:
_setmode = None
import io
from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END)
from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END, Reader, Writer) # noqa: F401
valid_seek_flags = {0, 1, 2} # Hardwired values
if hasattr(os, 'SEEK_HOLE') :
valid_seek_flags.add(os.SEEK_HOLE)
valid_seek_flags.add(os.SEEK_DATA)
# open() uses st_blksize whenever we can
DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
# open() uses max(min(blocksize, 8 MiB), DEFAULT_BUFFER_SIZE)
# when the device block size is available.
DEFAULT_BUFFER_SIZE = 128 * 1024 # bytes
# NOTE: Base classes defined here are registered with the "official" ABCs
# defined in io.py. We don't use real inheritance though, because we don't want
@@ -123,10 +124,10 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
the size of a fixed-size chunk buffer. When no buffering argument is
given, the default buffering policy works as follows:
* Binary files are buffered in fixed-size chunks; the size of the buffer
is chosen using a heuristic trying to determine the underlying device's
"block size" and falling back on `io.DEFAULT_BUFFER_SIZE`.
On many systems, the buffer will typically be 4096 or 8192 bytes long.
* Binary files are buffered in fixed-size chunks; the size of the buffer
is max(min(blocksize, 8 MiB), DEFAULT_BUFFER_SIZE)
when the device block size is available.
On most systems, the buffer will typically be 128 kilobytes long.
* "Interactive" text files (files for which isatty() returns True)
use line buffering. Other text files use the policy described above
@@ -238,18 +239,11 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None,
result = raw
try:
line_buffering = False
if buffering == 1 or buffering < 0 and raw.isatty():
if buffering == 1 or buffering < 0 and raw._isatty_open_only():
buffering = -1
line_buffering = True
if buffering < 0:
buffering = DEFAULT_BUFFER_SIZE
try:
bs = os.fstat(raw.fileno()).st_blksize
except (OSError, AttributeError):
pass
else:
if bs > 1:
buffering = bs
buffering = max(min(raw._blksize, 8192 * 1024), DEFAULT_BUFFER_SIZE)
if buffering < 0:
raise ValueError("invalid buffering size")
if buffering == 0:
@@ -941,22 +935,22 @@ class BytesIO(BufferedIOBase):
return self.read(size)
def write(self, b):
if self.closed:
raise ValueError("write to closed file")
if isinstance(b, str):
raise TypeError("can't write str to binary stream")
with memoryview(b) as view:
if self.closed:
raise ValueError("write to closed file")
n = view.nbytes # Size of any bytes-like object
if n == 0:
return 0
pos = self._pos
if pos > len(self._buffer):
# Inserts null bytes between the current end of the file
# and the new write position.
padding = b'\x00' * (pos - len(self._buffer))
self._buffer += padding
self._buffer[pos:pos + n] = b
self._pos += n
if n == 0:
return 0
pos = self._pos
if pos > len(self._buffer):
# Pad buffer to pos with null bytes.
self._buffer.resize(pos)
self._buffer[pos:pos + n] = view
self._pos += n
return n
def seek(self, pos, whence=0):
@@ -1470,6 +1464,17 @@ class BufferedRandom(BufferedWriter, BufferedReader):
return BufferedWriter.write(self, b)
def _new_buffersize(bytes_read):
# Parallels _io/fileio.c new_buffersize
if bytes_read > 65536:
addend = bytes_read >> 3
else:
addend = 256 + bytes_read
if addend < DEFAULT_BUFFER_SIZE:
addend = DEFAULT_BUFFER_SIZE
return bytes_read + addend
class FileIO(RawIOBase):
_fd = -1
_created = False
@@ -1494,6 +1499,7 @@ class FileIO(RawIOBase):
"""
if self._fd >= 0:
# Have to close the existing file first.
self._stat_atopen = None
try:
if self._closefd:
os.close(self._fd)
@@ -1573,18 +1579,15 @@ class FileIO(RawIOBase):
os.set_inheritable(fd, False)
self._closefd = closefd
fdfstat = os.fstat(fd)
self._stat_atopen = os.fstat(fd)
try:
if stat.S_ISDIR(fdfstat.st_mode):
if stat.S_ISDIR(self._stat_atopen.st_mode):
raise IsADirectoryError(errno.EISDIR,
os.strerror(errno.EISDIR), file)
except AttributeError:
# Ignore the AttributeError if stat.S_ISDIR or errno.EISDIR
# don't exist.
pass
self._blksize = getattr(fdfstat, 'st_blksize', 0)
if self._blksize <= 1:
self._blksize = DEFAULT_BUFFER_SIZE
if _setmode:
# don't translate newlines (\r\n <=> \n)
@@ -1601,6 +1604,7 @@ class FileIO(RawIOBase):
if e.errno != errno.ESPIPE:
raise
except:
self._stat_atopen = None
if owned_fd is not None:
os.close(owned_fd)
raise
@@ -1629,6 +1633,17 @@ class FileIO(RawIOBase):
return ('<%s name=%r mode=%r closefd=%r>' %
(class_name, name, self.mode, self._closefd))
@property
def _blksize(self):
if self._stat_atopen is None:
return DEFAULT_BUFFER_SIZE
blksize = getattr(self._stat_atopen, "st_blksize", 0)
# WASI sets blsize to 0
if not blksize:
return DEFAULT_BUFFER_SIZE
return blksize
def _checkReadable(self):
if not self._readable:
raise UnsupportedOperation('File not open for reading')
@@ -1640,7 +1655,13 @@ class FileIO(RawIOBase):
def read(self, size=None):
"""Read at most size bytes, returned as bytes.
Only makes one system call, so less data may be returned than requested
If size is less than 0, read all bytes in the file making
multiple read calls. See ``FileIO.readall``.
Attempts to make only one system call, retrying only per
PEP 475 (EINTR). This means less data may be returned than
requested.
In non-blocking mode, returns None if no data is available.
Return an empty bytes object at EOF.
"""
@@ -1656,45 +1677,57 @@ class FileIO(RawIOBase):
def readall(self):
"""Read all data from the file, returned as bytes.
In non-blocking mode, returns as much as is immediately available,
or None if no data is available. Return an empty bytes object at EOF.
Reads until either there is an error or read() returns size 0
(indicates EOF). If the file is already at EOF, returns an
empty bytes object.
In non-blocking mode, returns as much data as could be read
before EAGAIN. If no data is available (EAGAIN is returned
before bytes are read) returns None.
"""
self._checkClosed()
self._checkReadable()
bufsize = DEFAULT_BUFFER_SIZE
if self._stat_atopen is None or self._stat_atopen.st_size <= 0:
bufsize = DEFAULT_BUFFER_SIZE
else:
# In order to detect end of file, need a read() of at least 1
# byte which returns size 0. Oversize the buffer by 1 byte so the
# I/O can be completed with two read() calls (one for all data, one
# for EOF) without needing to resize the buffer.
bufsize = self._stat_atopen.st_size + 1
if self._stat_atopen.st_size > 65536:
try:
pos = os.lseek(self._fd, 0, SEEK_CUR)
if self._stat_atopen.st_size >= pos:
bufsize = self._stat_atopen.st_size - pos + 1
except OSError:
pass
result = bytearray(bufsize)
bytes_read = 0
try:
pos = os.lseek(self._fd, 0, SEEK_CUR)
end = os.fstat(self._fd).st_size
if end >= pos:
bufsize = end - pos + 1
except OSError:
pass
result = bytearray()
while True:
if len(result) >= bufsize:
bufsize = len(result)
bufsize += max(bufsize, DEFAULT_BUFFER_SIZE)
n = bufsize - len(result)
try:
chunk = os.read(self._fd, n)
except BlockingIOError:
if result:
break
while n := os.readinto(self._fd, memoryview(result)[bytes_read:]):
bytes_read += n
if bytes_read >= len(result):
result.resize(_new_buffersize(bytes_read))
except BlockingIOError:
if not bytes_read:
return None
if not chunk: # reached the end of the file
break
result += chunk
assert len(result) - bytes_read >= 1, \
"os.readinto buffer size 0 will result in erroneous EOF / returns 0"
result.resize(bytes_read)
return bytes(result)
def readinto(self, b):
def readinto(self, buffer):
"""Same as RawIOBase.readinto()."""
m = memoryview(b).cast('B')
data = self.read(len(m))
n = len(data)
m[:n] = data
return n
self._checkClosed()
self._checkReadable()
try:
return os.readinto(self._fd, buffer)
except BlockingIOError:
return None
def write(self, b):
"""Write bytes b to file, return number written.
@@ -1744,6 +1777,7 @@ class FileIO(RawIOBase):
if size is None:
size = self.tell()
os.ftruncate(self._fd, size)
self._stat_atopen = None
return size
def close(self):
@@ -1753,6 +1787,7 @@ class FileIO(RawIOBase):
called more than once without error.
"""
if not self.closed:
self._stat_atopen = None
try:
if self._closefd and self._fd >= 0:
os.close(self._fd)
@@ -1791,6 +1826,21 @@ class FileIO(RawIOBase):
self._checkClosed()
return os.isatty(self._fd)
def _isatty_open_only(self):
"""Checks whether the file is a TTY using an open-only optimization.
TTYs are always character devices. If the interpreter knows a file is
not a character device when it would call ``isatty``, can skip that
call. Inside ``open()`` there is a fresh stat result that contains that
information. Use the stat result to skip a system call. Outside of that
context TOCTOU issues (the fd could be arbitrarily modified by
surrounding code).
"""
if (self._stat_atopen is not None
and not stat.S_ISCHR(self._stat_atopen.st_mode)):
return False
return os.isatty(self._fd)
@property
def closefd(self):
"""True if the file descriptor will be closed by close()."""
@@ -2015,8 +2065,7 @@ class TextIOWrapper(TextIOBase):
raise ValueError("invalid encoding: %r" % encoding)
if not codecs.lookup(encoding)._is_text_encoding:
msg = ("%r is not a text encoding; "
"use codecs.open() to handle arbitrary codecs")
msg = "%r is not a text encoding"
raise LookupError(msg % encoding)
if errors is None:
@@ -2524,9 +2573,12 @@ class TextIOWrapper(TextIOBase):
size = size_index()
decoder = self._decoder or self._get_decoder()
if size < 0:
chunk = self.buffer.read()
if chunk is None:
raise BlockingIOError("Read returned None.")
# Read everything.
result = (self._get_decoded_chars() +
decoder.decode(self.buffer.read(), final=True))
decoder.decode(chunk, final=True))
if self._snapshot is not None:
self._set_decoded_chars('')
self._snapshot = None
Vendored
+55 -4
View File
@@ -46,21 +46,20 @@ __all__ = ["BlockingIOError", "open", "open_code", "IOBase", "RawIOBase",
"BufferedReader", "BufferedWriter", "BufferedRWPair",
"BufferedRandom", "TextIOBase", "TextIOWrapper",
"UnsupportedOperation", "SEEK_SET", "SEEK_CUR", "SEEK_END",
"DEFAULT_BUFFER_SIZE", "text_encoding", "IncrementalNewlineDecoder"]
"DEFAULT_BUFFER_SIZE", "text_encoding", "IncrementalNewlineDecoder",
"Reader", "Writer"]
import _io
import abc
from _collections_abc import _check_methods
from _io import (DEFAULT_BUFFER_SIZE, BlockingIOError, UnsupportedOperation,
open, open_code, FileIO, BytesIO, StringIO, BufferedReader,
BufferedWriter, BufferedRWPair, BufferedRandom,
IncrementalNewlineDecoder, text_encoding, TextIOWrapper)
# Pretend this exception was created here.
UnsupportedOperation.__module__ = "io"
# for seek()
SEEK_SET = 0
SEEK_CUR = 1
@@ -97,3 +96,55 @@ except ImportError:
pass
else:
RawIOBase.register(_WindowsConsoleIO)
#
# Static Typing Support
#
GenericAlias = type(list[int])
class Reader(metaclass=abc.ABCMeta):
"""Protocol for simple I/O reader instances.
This protocol only supports blocking I/O.
"""
__slots__ = ()
@abc.abstractmethod
def read(self, size=..., /):
"""Read data from the input stream and return it.
If *size* is specified, at most *size* items (bytes/characters) will be
read.
"""
@classmethod
def __subclasshook__(cls, C):
if cls is Reader:
return _check_methods(C, "read")
return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
class Writer(metaclass=abc.ABCMeta):
"""Protocol for simple I/O writer instances.
This protocol only supports blocking I/O.
"""
__slots__ = ()
@abc.abstractmethod
def write(self, data, /):
"""Write *data* to the output stream and return the number of items written."""
@classmethod
def __subclasshook__(cls, C):
if cls is Writer:
return _check_methods(C, "write")
return NotImplemented
__class_getitem__ = classmethod(GenericAlias)
+1 -1
View File
@@ -28,7 +28,7 @@ class BufferSizeTest:
f.write(b"\n")
f.write(s)
f.close()
f = open(os_helper.TESTFN, "rb")
f = self.open(os_helper.TESTFN, "rb")
line = f.readline()
self.assertEqual(line, s + b"\n")
line = f.readline()
+148 -3
View File
@@ -10,8 +10,8 @@ from weakref import proxy
from functools import wraps
from test.support import (
cpython_only, swap_attr, gc_collect, is_emscripten, is_wasi,
infinite_recursion,
cpython_only, swap_attr, gc_collect, is_wasi,
infinite_recursion, strace_helper
)
from test.support.os_helper import (
TESTFN, TESTFN_ASCII, TESTFN_UNICODE, make_bad_fd,
@@ -24,6 +24,9 @@ import _io # C implementation of io
import _pyio # Python implementation of io
_strace_flags=["--trace=%file,%desc"]
class AutoFileTests:
# file tests for which a test file is automatically set up
@@ -359,6 +362,144 @@ class AutoFileTests:
a = array('b', b'x'*10)
f.readinto(a)
@unittest.skip("TODO: RUSTPYTHON; extra ioctl(TCGETS) syscall for isatty check")
@strace_helper.requires_strace()
def test_syscalls_read(self):
"""Check set of system calls during common I/O patterns
It's expected as bits of the I/O implementation change, this will need
to change. The goal is to catch changes that unintentionally add
additional systemcalls (ex. additional calls have been looked at in
bpo-21679 and gh-120754).
"""
self.f.write(b"Hello, World!")
self.f.close()
def check_readall(name, code, prelude="", cleanup="",
extra_checks=None):
with self.subTest(name=name):
syscalls = strace_helper.get_events(code, _strace_flags,
prelude=prelude,
cleanup=cleanup)
# Some system calls (ex. mmap) can be used for both File I/O and
# memory allocation. Filter out the ones used for memory
# allocation.
syscalls = strace_helper.filter_memory(syscalls)
# The first call should be an open that returns a
# file descriptor (fd). Afer that calls may vary. Once the file
# is opened, check calls refer to it by fd as the filename
# could be removed from the filesystem, renamed, etc. See:
# Time-of-check time-of-use (TOCTOU) software bug class.
#
# There are a number of related but distinct open system calls
# so not checking precise name here.
self.assertGreater(
len(syscalls),
1,
f"Should have had at least an open call|calls={syscalls}")
fd_str = syscalls[0].returncode
# All other calls should contain the fd in their argument set.
for ev in syscalls[1:]:
self.assertIn(
fd_str,
ev.args,
f"Looking for file descriptor in arguments|ev={ev}"
)
# There are a number of related syscalls used to implement
# behaviors in a libc (ex. fstat, newfstatat, statx, open, openat).
# Allow any that use the same substring.
def count_similarname(name):
return len([ev for ev in syscalls if name in ev.syscall])
checks = [
# Should open and close the file exactly once
("open", 1),
("close", 1),
# There should no longer be an isatty call (All files being
# tested are block devices / not character devices).
('ioctl', 0),
# Should only have one fstat (bpo-21679, gh-120754)
# note: It's important this uses a fd rather than filename,
# That is validated by the `fd` check above.
# note: fstat, newfstatat, and statx have all been observed
# here in the underlying C library implementations.
("stat", 1)
]
if extra_checks:
checks += extra_checks
for call, count in checks:
self.assertEqual(
count_similarname(call),
count,
msg=f"call={call}|count={count}|syscalls={syscalls}"
)
# "open, read, close" file using different common patterns.
check_readall(
"open builtin with default options",
f"""
f = open('{TESTFN}')
f.read()
f.close()
"""
)
check_readall(
"open in binary mode",
f"""
f = open('{TESTFN}', 'rb')
f.read()
f.close()
"""
)
check_readall(
"open in text mode",
f"""
f = open('{TESTFN}', 'rt')
f.read()
f.close()
""",
# GH-122111: read_text uses BufferedIO which requires looking up
# position in file. `read_bytes` disables that buffering and avoids
# these calls which is tested the `pathlib read_bytes` case.
extra_checks=[("seek", 1)]
)
check_readall(
"pathlib read_bytes",
"p.read_bytes()",
prelude=f"""from pathlib import Path; p = Path("{TESTFN}")""",
# GH-122111: Buffering is disabled so these calls are avoided.
extra_checks=[("seek", 0)]
)
check_readall(
"pathlib read_text",
"p.read_text()",
prelude=f"""from pathlib import Path; p = Path("{TESTFN}")"""
)
# Focus on just `read()`.
calls = strace_helper.get_syscalls(
prelude=f"f = open('{TESTFN}')",
code="f.read()",
cleanup="f.close()",
strace_flags=_strace_flags
)
# One to read all the bytes
# One to read the EOF and get a size 0 return.
self.assertEqual(calls.count("read"), 2)
class CAutoFileTests(AutoFileTests, unittest.TestCase):
FileIO = _io.FileIO
modulename = '_io'
@@ -367,6 +508,10 @@ class PyAutoFileTests(AutoFileTests, unittest.TestCase):
FileIO = _pyio.FileIO
modulename = '_pyio'
@unittest.expectedFailureIfWindows('TODO: RUSTPYTHON; _blksize mismatch on Windows')
def testBlksize(self):
return super().testBlksize()
class OtherFileTests:
@@ -391,7 +536,7 @@ class OtherFileTests:
self.assertEqual(f.isatty(), False)
f.close()
if sys.platform != "win32" and not is_emscripten:
if sys.platform != "win32":
try:
f = self.FileIO("/dev/tty", "a")
except OSError:
+19 -4
View File
@@ -4071,8 +4071,6 @@ class TextIOWrapperTest(unittest.TestCase):
self.assertEqual(newtxt.tag, 'ham')
del MyTextIO
# TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'NoneType'
@unittest.expectedFailure
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
def test_read_non_blocking(self):
import os
@@ -4113,6 +4111,11 @@ class CTextIOWrapperTest(TextIOWrapperTest):
io = io
shutdown_error = "LookupError: unknown encoding: ascii"
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.expectedFailure
def test_read_non_blocking(self):
return super().test_read_non_blocking()
def test_initialization(self):
r = self.BytesIO(b"\xc3\xa9\n\n")
b = self.BufferedReader(r, 1000)
@@ -4231,15 +4234,29 @@ class CTextIOWrapperTest(TextIOWrapperTest):
def test_seek_with_encoder_state(self):
return super().test_seek_with_encoder_state()
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'NoneType'
def test_read_non_blocking(self):
return super().test_read_non_blocking()
class PyTextIOWrapperTest(TextIOWrapperTest):
io = pyio
shutdown_error = "LookupError: unknown encoding: ascii"
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.set_blocking not available on Windows")
def test_read_non_blocking(self):
return super().test_read_non_blocking()
@unittest.expectedFailure # TODO: RUSTPYTHON; LookupError: unknown encoding: euc_jis_2004
def test_seek_with_encoder_state(self):
return super().test_seek_with_encoder_state()
if sys.platform == "win32":
@unittest.skipUnless(hasattr(os, "pipe"), "requires os.pipe()")
@unittest.expectedFailure
def test_read_non_blocking(self):
return super().test_read_non_blocking()
class IncrementalNewlineDecoderTest(unittest.TestCase):
@@ -5097,12 +5114,10 @@ class ProtocolsTest(unittest.TestCase):
def write(self, b: bytes):
pass
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'io' has no attribute 'Reader'
def test_reader_subclass(self):
self.assertIsSubclass(self.MyReader, io.Reader)
self.assertNotIsSubclass(str, io.Reader)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: module 'io' has no attribute 'Writer'
def test_writer_subclass(self):
self.assertIsSubclass(self.MyWriter, io.Writer)
self.assertNotIsSubclass(str, io.Writer)
+205 -160
View File
@@ -6,10 +6,12 @@ BytesIO -- for bytes
import unittest
from test import support
import gc
import io
import _pyio as pyio
import pickle
import sys
import weakref
class IntLike:
def __init__(self, num):
@@ -52,6 +54,12 @@ class MemorySeekTestMixin:
self.assertEqual(buf[3:], bytesIo.read())
self.assertRaises(TypeError, bytesIo.seek, 0.0)
self.assertEqual(sys.maxsize, bytesIo.seek(sys.maxsize))
self.assertEqual(self.EOF, bytesIo.read(4))
self.assertEqual(sys.maxsize - 2, bytesIo.seek(sys.maxsize - 2))
self.assertEqual(self.EOF, bytesIo.read(4))
def testTell(self):
buf = self.buftype("1234567890")
bytesIo = self.ioclass(buf)
@@ -263,8 +271,8 @@ class MemoryTestMixin:
memio = self.ioclass(buf * 10)
self.assertEqual(iter(memio), memio)
self.assertTrue(hasattr(memio, '__iter__'))
self.assertTrue(hasattr(memio, '__next__'))
self.assertHasAttr(memio, '__iter__')
self.assertHasAttr(memio, '__next__')
i = 0
for line in memio:
self.assertEqual(line, buf)
@@ -463,6 +471,40 @@ class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
memio.close()
self.assertRaises(ValueError, memio.getbuffer)
def test_getbuffer_empty(self):
memio = self.ioclass()
buf = memio.getbuffer()
self.assertEqual(bytes(buf), b"")
# Trying to change the size of the BytesIO while a buffer is exported
# raises a BufferError.
self.assertRaises(BufferError, memio.write, b'x')
buf2 = memio.getbuffer()
self.assertRaises(BufferError, memio.write, b'x')
buf.release()
self.assertRaises(BufferError, memio.write, b'x')
buf2.release()
memio.write(b'x')
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: <memory at 0xbb894d200> is not None
def test_getbuffer_gc_collect(self):
memio = self.ioclass(b"1234567890")
buf = memio.getbuffer()
memiowr = weakref.ref(memio)
bufwr = weakref.ref(buf)
# Create a reference loop.
a = [buf]
a.append(a)
# The Python implementation emits an unraisable exception.
with support.catch_unraisable_exception():
del memio
del buf
del a
# The C implementation emits an unraisable exception.
with support.catch_unraisable_exception():
gc.collect()
self.assertIsNone(memiowr())
self.assertIsNone(bufwr())
def test_read1(self):
buf = self.buftype("1234567890")
self.assertEqual(self.ioclass(buf).read1(), buf)
@@ -517,6 +559,14 @@ class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
memio.seek(1, 1)
self.assertEqual(memio.read(), buf[1:])
def test_issue141311(self):
memio = self.ioclass()
# Seek allows PY_SSIZE_T_MAX, read should handle that.
# Past end of buffer read should always return 0 (EOF).
self.assertEqual(sys.maxsize, memio.seek(sys.maxsize))
buf = bytearray(2)
self.assertEqual(0, memio.readinto(buf))
def test_unicode(self):
memio = self.ioclass()
@@ -538,6 +588,75 @@ class PyBytesIOTest(MemoryTestMixin, MemorySeekTestMixin, unittest.TestCase):
self.ioclass(initial_bytes=buf)
self.assertRaises(TypeError, self.ioclass, buf, foo=None)
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'B'
def test_write_concurrent_close(self):
class B:
def __buffer__(self, flags):
memio.close()
return memoryview(b"A")
memio = self.ioclass()
self.assertRaises(ValueError, memio.write, B())
# Prevent crashes when memio.write() or memio.writelines()
# concurrently mutates (e.g., closes or exports) 'memio'.
# See: https://github.com/python/cpython/issues/143378.
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'B'
def test_writelines_concurrent_close(self):
class B:
def __buffer__(self, flags):
memio.close()
return memoryview(b"A")
memio = self.ioclass()
self.assertRaises(ValueError, memio.writelines, [B()])
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'B'
def test_write_concurrent_export(self):
class B:
buf = None
def __buffer__(self, flags):
self.buf = memio.getbuffer()
return memoryview(b"A")
memio = self.ioclass()
self.assertRaises(BufferError, memio.write, B())
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'B'
def test_writelines_concurrent_export(self):
class B:
buf = None
def __buffer__(self, flags):
self.buf = memio.getbuffer()
return memoryview(b"A")
memio = self.ioclass()
self.assertRaises(BufferError, memio.writelines, [B()])
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: a bytes-like object is required, not 'B'
def test_write_mutating_buffer(self):
# Test that buffer is exported only once during write().
# See: https://github.com/python/cpython/issues/143602.
class B:
count = 0
def __buffer__(self, flags):
self.count += 1
if self.count == 1:
return memoryview(b"AAA")
else:
return memoryview(b"BBBBBBBBB")
memio = self.ioclass(b'0123456789')
memio.seek(2)
b = B()
n = memio.write(b)
self.assertEqual(b.count, 1)
self.assertEqual(n, 3)
self.assertEqual(memio.getvalue(), b"01AAA56789")
self.assertEqual(memio.tell(), 5)
class TextIOTestMixin:
@@ -724,54 +843,6 @@ class CBytesIOTest(PyBytesIOTest):
ioclass = io.BytesIO
UnsupportedOperation = io.UnsupportedOperation
def test_bytes_array(self):
super().test_bytes_array()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_flags(self):
super().test_flags()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_getbuffer(self):
super().test_getbuffer()
def test_init(self):
super().test_init()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_issue5449(self):
super().test_issue5449()
def test_read(self):
super().test_read()
def test_readline(self):
super().test_readline()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_relative_seek(self):
super().test_relative_seek()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_seek(self):
super().test_seek()
def test_subclassing(self):
super().test_subclassing()
def test_truncate(self):
super().test_truncate()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_write(self):
super().test_write()
def test_getstate(self):
memio = self.ioclass()
state = memio.__getstate__()
@@ -783,8 +854,7 @@ class CBytesIOTest(PyBytesIOTest):
memio.close()
self.assertRaises(ValueError, memio.__getstate__)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'bytes' but 'bytearray' found.
def test_setstate(self):
# This checks whether __setstate__ does proper input validation.
memio = self.ioclass()
@@ -816,7 +886,7 @@ class CBytesIOTest(PyBytesIOTest):
def _test_cow_mutation(self, mutation):
# Common code for all BytesIO copy-on-write mutation tests.
imm = b' ' * 1024
imm = (' ' * 1024).encode("ascii")
old_rc = sys.getrefcount(imm)
memio = self.ioclass(imm)
self.assertEqual(sys.getrefcount(imm), old_rc + 1)
@@ -857,79 +927,25 @@ class CBytesIOTest(PyBytesIOTest):
memio = self.ioclass(ba)
self.assertEqual(sys.getrefcount(ba), old_rc)
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised by writable
def test_flags(self):
return super().test_flags()
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised by write
def test_write(self):
return super().test_write()
@unittest.expectedFailure # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust u64
def test_seek(self):
return super().test_seek()
class CStringIOTest(PyStringIOTest):
ioclass = io.StringIO
UnsupportedOperation = io.UnsupportedOperation
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_detach(self):
super().test_detach()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_flags(self):
super().test_flags()
def test_init(self):
super().test_init()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_issue5265(self):
super().test_issue5265()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_cr(self):
super().test_newline_cr()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_crlf(self):
super().test_newline_crlf()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_empty(self):
super().test_newline_empty()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_none(self):
super().test_newline_none()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newlines_property(self):
super().test_newlines_property()
def test_read(self):
super().test_read()
def test_readline(self):
super().test_readline()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_relative_seek(self):
super().test_relative_seek()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_seek(self):
super().test_seek()
def test_textio_properties(self):
super().test_textio_properties()
def test_truncate(self):
super().test_truncate()
# XXX: For the Python version of io.StringIO, this is highly
# dependent on the encoding used for the underlying buffer.
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: 8 != 2
def test_widechar(self):
buf = self.buftype("\U0002030a\U00020347")
memio = self.ioclass(buf)
@@ -954,8 +970,7 @@ class CStringIOTest(PyStringIOTest):
memio.close()
self.assertRaises(ValueError, memio.__getstate__)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised by __setstate__
def test_setstate(self):
# This checks whether __setstate__ does proper input validation.
memio = self.ioclass()
@@ -973,54 +988,84 @@ class CStringIOTest(PyStringIOTest):
memio.close()
self.assertRaises(ValueError, memio.__setstate__, ("closed", "", 0, None))
@unittest.expectedFailure # TODO: RUSTPYTHON; +
def test_issue5265(self):
return super().test_issue5265()
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ++++
def test_newline_empty(self):
return super().test_newline_empty()
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ^^^^^
def test_newline_none(self):
return super().test_newline_none()
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: OSError not raised by seek
def test_relative_seek(self):
return super().test_relative_seek()
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ValueError not raised by writable
def test_flags(self):
return super().test_flags()
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'StringIO' object has no attribute 'detach'
def test_detach(self):
return super().test_detach()
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'StringIO' object has no attribute 'newlines'. Did you mean: 'readlines'?
def test_newlines_property(self):
return super().test_newlines_property()
@unittest.expectedFailure # TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust u64
def test_seek(self):
return super().test_seek()
@unittest.expectedFailure # TODO: RUSTPYTHON; d
def test_newline_cr(self):
return super().test_newline_cr()
@unittest.expectedFailure # TODO: RUSTPYTHON; d
def test_newline_crlf(self):
return super().test_newline_crlf()
class CStringIOPickleTest(PyStringIOPickleTest):
UnsupportedOperation = io.UnsupportedOperation
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_issue5265(self):
super().test_issue5265()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_cr(self):
super().test_newline_cr()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_crlf(self):
super().test_newline_crlf()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_empty(self):
super().test_newline_empty()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newline_none(self):
super().test_newline_none()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_newlines_property(self):
super().test_newlines_property()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_relative_seek(self):
super().test_relative_seek()
def test_textio_properties(self):
super().test_textio_properties()
class ioclass(io.StringIO):
def __new__(cls, *args, **kwargs):
return pickle.loads(pickle.dumps(io.StringIO(*args, **kwargs)))
def __init__(self, *args, **kwargs):
pass
@unittest.expectedFailure # TODO: RUSTPYTHON; +
def test_issue5265(self):
return super().test_issue5265()
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ++++
def test_newline_empty(self):
return super().test_newline_empty()
@unittest.expectedFailure # TODO: RUSTPYTHON; ? ^^^^^
def test_newline_none(self):
return super().test_newline_none()
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: OSError not raised by seek
def test_relative_seek(self):
return super().test_relative_seek()
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'StringIO' object has no attribute 'newlines'. Did you mean: 'readlines'?
def test_newlines_property(self):
return super().test_newlines_property()
@unittest.expectedFailure # TODO: RUSTPYTHON; d
def test_newline_cr(self):
return super().test_newline_cr()
@unittest.expectedFailure # TODO: RUSTPYTHON; d
def test_newline_crlf(self):
return super().test_newline_crlf()
if __name__ == '__main__':
unittest.main()