summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/io.rst5
-rw-r--r--Lib/_pyio.py4
-rw-r--r--Lib/test/test_io.py44
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_io/iobase.c4
5 files changed, 45 insertions, 15 deletions
diff --git a/Doc/library/io.rst b/Doc/library/io.rst
index b2e586a..faf1aea 100644
--- a/Doc/library/io.rst
+++ b/Doc/library/io.rst
@@ -361,8 +361,9 @@ I/O Base Classes
.. method:: readinto(b)
- Read up to len(b) bytes into bytearray *b* and return the number of bytes
- read.
+ Read up to len(b) bytes into bytearray *b* and return the number ofbytes
+ read. If the object is in non-blocking mode and no bytes are available,
+ ``None`` is returned.
.. method:: write(b)
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index ffbfda1..4485233 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -542,6 +542,8 @@ class RawIOBase(IOBase):
return self.readall()
b = bytearray(n.__index__())
n = self.readinto(b)
+ if n is None:
+ return None
del b[n:]
return bytes(b)
@@ -559,7 +561,7 @@ class RawIOBase(IOBase):
"""Read up to len(b) bytes into b.
Returns number of bytes read (0 for EOF), or None if the object
- is set not to block as has no data to read.
+ is set not to block and has no data to read.
"""
self._unsupported("readinto")
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index 3a3e074..4c7ce31 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -47,7 +47,9 @@ def _default_chunk_size():
return f._CHUNK_SIZE
-class MockRawIO:
+class MockRawIOWithoutRead:
+ """A RawIO implementation without read(), so as to exercise the default
+ RawIO.read() which calls readinto()."""
def __init__(self, read_stack=()):
self._read_stack = list(read_stack)
@@ -55,14 +57,6 @@ class MockRawIO:
self._reads = 0
self._extraneous_reads = 0
- def read(self, n=None):
- self._reads += 1
- try:
- return self._read_stack.pop(0)
- except:
- self._extraneous_reads += 1
- return b""
-
def write(self, b):
self._write_stack.append(bytes(b))
return len(b)
@@ -109,6 +103,23 @@ class MockRawIO:
def truncate(self, pos=None):
return pos
+class CMockRawIOWithoutRead(MockRawIOWithoutRead, io.RawIOBase):
+ pass
+
+class PyMockRawIOWithoutRead(MockRawIOWithoutRead, pyio.RawIOBase):
+ pass
+
+
+class MockRawIO(MockRawIOWithoutRead):
+
+ def read(self, n=None):
+ self._reads += 1
+ try:
+ return self._read_stack.pop(0)
+ except:
+ self._extraneous_reads += 1
+ return b""
+
class CMockRawIO(MockRawIO, io.RawIOBase):
pass
@@ -554,6 +565,19 @@ class IOTest(unittest.TestCase):
f.close()
self.assertRaises(ValueError, f.flush)
+ def test_RawIOBase_read(self):
+ # Exercise the default RawIOBase.read() implementation (which calls
+ # readinto() internally).
+ rawio = self.MockRawIOWithoutRead((b"abc", b"d", None, b"efg", None))
+ self.assertEqual(rawio.read(2), b"ab")
+ self.assertEqual(rawio.read(2), b"c")
+ self.assertEqual(rawio.read(2), b"d")
+ self.assertEqual(rawio.read(2), None)
+ self.assertEqual(rawio.read(2), b"ef")
+ self.assertEqual(rawio.read(2), b"g")
+ self.assertEqual(rawio.read(2), None)
+ self.assertEqual(rawio.read(2), b"")
+
class CIOTest(IOTest):
pass
@@ -2557,7 +2581,7 @@ def test_main():
# Put the namespaces of the IO module we are testing and some useful mock
# classes in the __dict__ of each test.
mocks = (MockRawIO, MisbehavedRawIO, MockFileIO, CloseFailureIO,
- MockNonBlockWriterIO)
+ MockNonBlockWriterIO, MockRawIOWithoutRead)
all_members = io.__all__ + ["IncrementalNewlineDecoder"]
c_io_ns = {name : getattr(io, name) for name in all_members}
py_io_ns = {name : getattr(pyio, name) for name in all_members}
diff --git a/Misc/NEWS b/Misc/NEWS
index 55160ca..2932280 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -117,6 +117,9 @@ C-API
Library
-------
+- Issue #9854: The default read() implementation in io.RawIOBase now
+ handles non-blocking readinto() returning None correctly.
+
- Issue #9853: Fix the signature of SSLSocket.recvfrom() and
SSLSocket.sendto() to match the corresponding socket methods.
diff --git a/Modules/_io/iobase.c b/Modules/_io/iobase.c
index 2d664ab..dfc1a3a 100644
--- a/Modules/_io/iobase.c
+++ b/Modules/_io/iobase.c
@@ -776,9 +776,9 @@ rawiobase_read(PyObject *self, PyObject *args)
return NULL;
res = PyObject_CallMethodObjArgs(self, _PyIO_str_readinto, b, NULL);
- if (res == NULL) {
+ if (res == NULL || res == Py_None) {
Py_DECREF(b);
- return NULL;
+ return res;
}
n = PyNumber_AsSsize_t(res, PyExc_ValueError);