# HG changeset patch # Parent 4e84e45e191b3b063387fdd68075f47f4576e215 Issue #22671: Clarify and test default read method implementations diff -r 4e84e45e191b Doc/library/io.rst --- a/Doc/library/io.rst Fri Dec 19 11:21:56 2014 -0500 +++ b/Doc/library/io.rst Sat Dec 20 12:24:32 2014 +0000 @@ -370,23 +370,27 @@ (this is left to Buffered I/O and Text I/O, described later in this page). In addition to the attributes and methods from :class:`IOBase`, - :class:`RawIOBase` provides the following methods: + :class:`RawIOBase` defines the following methods: .. method:: read(size=-1) - Read up to *size* bytes from the object and return them. As a convenience, - if *size* is unspecified or -1, :meth:`readall` is called. Otherwise, - only one system call is ever made. Fewer than *size* bytes may be + Read up to *size* bytes from the object and return them. + Only one system call is ever made, unless *size* is + unspecified or -1. Fewer than *size* bytes may be returned if the operating system call returns fewer than *size* bytes. If 0 bytes are returned, and *size* was not 0, this indicates end of file. If the object is in non-blocking mode and no bytes are available, ``None`` is returned. + As a convenience, a default implementation is provided which + defers to :meth:`readall` or :meth:`readinto` as appropriate. + .. method:: readall() Read and return all the bytes from the stream until EOF, using multiple - calls to the stream if necessary. + calls to the stream if necessary. A default implementation is + provided. .. method:: readinto(b) @@ -426,8 +430,8 @@ :class:`RawIOBase` implementation, but wrap one, like :class:`BufferedWriter` and :class:`BufferedReader` do. - :class:`BufferedIOBase` provides or overrides these methods and attribute in - addition to those from :class:`IOBase`: + :class:`BufferedIOBase` defines these methods and attribute, + adding to and overriding those from :class:`IOBase`: .. attribute:: raw @@ -477,7 +481,8 @@ bytes read. Like :meth:`read`, multiple reads may be issued to the underlying raw - stream, unless the latter is interactive. + stream, unless the latter is interactive. A default implementation is + provided. A :exc:`BlockingIOError` is raised if the underlying raw stream is in non blocking-mode, and has no data available at the moment. @@ -487,6 +492,7 @@ Read up to ``len(b)`` bytes into bytearray *b*, ,using at most one call to the underlying raw stream's :meth:`~RawIOBase.read` (or :meth:`~RawIOBase.readinto`) method. Return the number of bytes read. + A default implementation is provided. A :exc:`BlockingIOError` is raised if the underlying raw stream is in non blocking-mode, and has no data available at the moment. diff -r 4e84e45e191b Lib/test/test_io.py --- a/Lib/test/test_io.py Fri Dec 19 11:21:56 2014 -0500 +++ b/Lib/test/test_io.py Sat Dec 20 12:24:32 2014 +0000 @@ -605,8 +605,8 @@ self.assertRaises(ValueError, f.flush) def test_RawIOBase_read(self): - # Exercise the default RawIOBase.read() implementation (which calls - # readinto() internally). + # Exercise the default RawIOBase.read() and readall() implementations + # (which call 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") @@ -617,6 +617,43 @@ self.assertEqual(rawio.read(2), None) self.assertEqual(rawio.read(2), b"") + rawio = self.MockRawIOWithoutRead((b"abc", b"d", b"efg")) + self.assertEqual(rawio.read(), b"abcdefg") + rawio = self.MockRawIOWithoutRead((b"abc", b"d", b"efg")) + self.assertEqual(rawio.readall(), b"abcdefg") + + def test_BufferedIOBase_readinto(self): + # Exercise the default BufferedIOBase.readinto() and readinto1() + # implementations (which call read() or read1() internally). + class Reader(self.BufferedIOBase): + def read(self, size): + result = self.avail[:size] + self.avail = self.avail[size:] + return result + def read1(self, size): + return self.read(min(size, 5)) + tests = ( + dict(method="readinto", avail=10, request=5, result=5), + dict(method="readinto", avail=10, request=6, result=6), + dict(method="readinto", avail=5, request=6, result=5), + dict(method="readinto", avail=6, request=7, result=6), + dict(method="readinto1", avail=10, request=5, result=5), + dict(method="readinto1", avail=10, request=6, result=5), + dict(method="readinto1", avail=5, request=6, result=5), + dict(method="readinto1", avail=6, request=7, result=5), + ) + for test in tests: + with self.subTest(**test): + reader = Reader() + reader.avail = bytes(range(test["avail"])) + buffer = bytearray(test["request"]) + call = getattr(reader, test["method"]) + size = test["result"] + self.assertEqual(call(buffer), size) + self.assertEqual(len(buffer), test["request"]) + self.assertSequenceEqual(buffer[:size], range(size)) + self.assertEqual(len(reader.avail), test["avail"] - size) + def test_types_have_dict(self): test = ( self.IOBase(),