diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2009-04-19 00:09:36 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2009-04-19 00:09:36 (GMT) |
commit | cf4c749680bc178f2afb469f8828790a7268d137 (patch) | |
tree | b82b665246bec0aa1ab1c099e72769ebc3ee70a7 | |
parent | 561f36068f9a6ec70a49646a4ca78a411929a73c (diff) | |
download | cpython-cf4c749680bc178f2afb469f8828790a7268d137.zip cpython-cf4c749680bc178f2afb469f8828790a7268d137.tar.gz cpython-cf4c749680bc178f2afb469f8828790a7268d137.tar.bz2 |
Issue #5734: BufferedRWPair was poorly tested and had several glaring bugs.
Patch by Brian Quinlan.
-rw-r--r-- | Lib/_pyio.py | 17 | ||||
-rw-r--r-- | Lib/test/test_io.py | 103 | ||||
-rw-r--r-- | Misc/NEWS | 3 | ||||
-rw-r--r-- | Modules/_io/bufferedio.c | 43 |
4 files changed, 137 insertions, 29 deletions
diff --git a/Lib/_pyio.py b/Lib/_pyio.py index 0ef6822..fe020fd 100644 --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -839,7 +839,9 @@ class BufferedReader(_BufferedIOMixin): def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE): """Create a new buffered reader using the given readable raw IO object. """ - raw._checkReadable() + if not raw.readable(): + raise IOError('"raw" argument must be readable.') + _BufferedIOMixin.__init__(self, raw) if buffer_size <= 0: raise ValueError("invalid buffer size") @@ -970,7 +972,9 @@ class BufferedWriter(_BufferedIOMixin): def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE, max_buffer_size=None): - raw._checkWritable() + if not raw.writable(): + raise IOError('"raw" argument must be writable.') + _BufferedIOMixin.__init__(self, raw) if buffer_size <= 0: raise ValueError("invalid buffer size") @@ -1076,8 +1080,13 @@ class BufferedRWPair(BufferedIOBase): """ if max_buffer_size is not None: warnings.warn("max_buffer_size is deprecated", DeprecationWarning, 2) - reader._checkReadable() - writer._checkWritable() + + if not reader.readable(): + raise IOError('"reader" argument must be readable.') + + if not writer.writable(): + raise IOError('"writer" argument must be writable.') + self.reader = BufferedReader(reader, buffer_size) self.writer = BufferedWriter(writer, buffer_size) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 74dbfd2..9ae1d28 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -1051,13 +1051,11 @@ class PyBufferedWriterTest(BufferedWriterTest): class BufferedRWPairTest(unittest.TestCase): - def test_basic(self): - r = self.MockRawIO(()) - w = self.MockRawIO() - pair = self.tp(r, w) + def test_constructor(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) self.assertFalse(pair.closed) - def test_max_buffer_size_deprecation(self): + def test_constructor_max_buffer_size_deprecation(self): with support.check_warnings() as w: warnings.simplefilter("always", DeprecationWarning) self.tp(self.MockRawIO(), self.MockRawIO(), 8, 12) @@ -1067,7 +1065,100 @@ class BufferedRWPairTest(unittest.TestCase): self.assertEqual(str(warning.message), "max_buffer_size is deprecated") - # XXX More Tests + def test_constructor_with_not_readable(self): + class NotReadable(MockRawIO): + def readable(self): + return False + + self.assertRaises(IOError, self.tp, NotReadable(), self.MockRawIO()) + + def test_constructor_with_not_writeable(self): + class NotWriteable(MockRawIO): + def writable(self): + return False + + self.assertRaises(IOError, self.tp, self.MockRawIO(), NotWriteable()) + + def test_read(self): + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + self.assertEqual(pair.read(3), b"abc") + self.assertEqual(pair.read(1), b"d") + self.assertEqual(pair.read(), b"ef") + + def test_read1(self): + # .read1() is delegated to the underlying reader object, so this test + # can be shallow. + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + self.assertEqual(pair.read1(3), b"abc") + + def test_readinto(self): + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + data = bytearray(5) + self.assertEqual(pair.readinto(data), 5) + self.assertEqual(data, b"abcde") + + def test_write(self): + w = self.MockRawIO() + pair = self.tp(self.MockRawIO(), w) + + pair.write(b"abc") + pair.flush() + pair.write(b"def") + pair.flush() + self.assertEqual(w._write_stack, [b"abc", b"def"]) + + def test_peek(self): + pair = self.tp(self.BytesIO(b"abcdef"), self.MockRawIO()) + + self.assertTrue(pair.peek(3).startswith(b"abc")) + self.assertEqual(pair.read(3), b"abc") + + def test_readable(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertTrue(pair.readable()) + + def test_writeable(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertTrue(pair.writable()) + + def test_seekable(self): + # BufferedRWPairs are never seekable, even if their readers and writers + # are. + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertFalse(pair.seekable()) + + # .flush() is delegated to the underlying writer object and has been + # tested in the test_write method. + + def test_close_and_closed(self): + pair = self.tp(self.MockRawIO(), self.MockRawIO()) + self.assertFalse(pair.closed) + pair.close() + self.assertTrue(pair.closed) + + def test_isatty(self): + class SelectableIsAtty(MockRawIO): + def __init__(self, isatty): + MockRawIO.__init__(self) + self._isatty = isatty + + def isatty(self): + return self._isatty + + pair = self.tp(SelectableIsAtty(False), SelectableIsAtty(False)) + self.assertFalse(pair.isatty()) + + pair = self.tp(SelectableIsAtty(True), SelectableIsAtty(False)) + self.assertTrue(pair.isatty()) + + pair = self.tp(SelectableIsAtty(False), SelectableIsAtty(True)) + self.assertTrue(pair.isatty()) + + pair = self.tp(SelectableIsAtty(True), SelectableIsAtty(True)) + self.assertTrue(pair.isatty()) class CBufferedRWPairTest(BufferedRWPairTest): tp = io.BufferedRWPair @@ -72,6 +72,9 @@ Core and Builtins Library ------- +- Issue #5734: BufferedRWPair was poorly tested and had several glaring + bugs. Patch by Brian Quinlan. + - Issue #1161031: fix readwrite select flag handling: POLLPRI now results in a handle_expt_event call, not handle_read_event, and POLLERR and POLLNVAL now call handle_close, not handle_expt_event. Also, diff --git a/Modules/_io/bufferedio.c b/Modules/_io/bufferedio.c index c3ca1cd..4b654a0 100644 --- a/Modules/_io/bufferedio.c +++ b/Modules/_io/bufferedio.c @@ -1846,7 +1846,7 @@ typedef struct { static int BufferedRWPair_init(BufferedRWPairObject *self, PyObject *args, - PyObject *kwds) + PyObject *kwds) { PyObject *reader, *writer; Py_ssize_t buffer_size = DEFAULT_BUFFER_SIZE; @@ -1865,29 +1865,18 @@ BufferedRWPair_init(BufferedRWPairObject *self, PyObject *args, if (_PyIOBase_checkWritable(writer, Py_True) == NULL) return -1; - args = Py_BuildValue("(n)", buffer_size); - if (args == NULL) { - Py_CLEAR(self->reader); - return -1; - } - self->reader = (BufferedObject *)PyType_GenericNew( - &PyBufferedReader_Type, args, NULL); - Py_DECREF(args); + self->reader = (BufferedObject *) PyObject_CallFunction( + (PyObject *) &PyBufferedReader_Type, "On", reader, buffer_size); if (self->reader == NULL) return -1; - args = Py_BuildValue("(n)", buffer_size); - if (args == NULL) { - Py_CLEAR(self->reader); - return -1; - } - self->writer = (BufferedObject *)PyType_GenericNew( - &PyBufferedWriter_Type, args, NULL); - Py_DECREF(args); + self->writer = (BufferedObject *) PyObject_CallFunction( + (PyObject *) &PyBufferedWriter_Type, "On", writer, buffer_size); if (self->writer == NULL) { Py_CLEAR(self->reader); return -1; } + return 0; } @@ -1952,6 +1941,12 @@ BufferedRWPair_read1(BufferedRWPairObject *self, PyObject *args) } static PyObject * +BufferedRWPair_readinto(BufferedRWPairObject *self, PyObject *args) +{ + return _forward_call(self->reader, "readinto", args); +} + +static PyObject * BufferedRWPair_write(BufferedRWPairObject *self, PyObject *args) { return _forward_call(self->writer, "write", args); @@ -2000,12 +1995,17 @@ BufferedRWPair_isatty(BufferedRWPairObject *self, PyObject *args) return _forward_call(self->reader, "isatty", args); } +static PyObject * +BufferedRWPair_closed_get(BufferedRWPairObject *self, void *context) +{ + return PyObject_GetAttr((PyObject *) self->writer, _PyIO_str_closed); +} static PyMethodDef BufferedRWPair_methods[] = { {"read", (PyCFunction)BufferedRWPair_read, METH_VARARGS}, {"peek", (PyCFunction)BufferedRWPair_peek, METH_VARARGS}, {"read1", (PyCFunction)BufferedRWPair_read1, METH_VARARGS}, - {"readinto", (PyCFunction)Buffered_readinto, METH_VARARGS}, + {"readinto", (PyCFunction)BufferedRWPair_readinto, METH_VARARGS}, {"write", (PyCFunction)BufferedRWPair_write, METH_VARARGS}, {"flush", (PyCFunction)BufferedRWPair_flush, METH_NOARGS}, @@ -2019,6 +2019,11 @@ static PyMethodDef BufferedRWPair_methods[] = { {NULL, NULL} }; +static PyGetSetDef BufferedRWPair_getset[] = { + {"closed", (getter)BufferedRWPair_closed_get, NULL, NULL}, + {0} +}; + PyTypeObject PyBufferedRWPair_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_io.BufferedRWPair", /*tp_name*/ @@ -2050,7 +2055,7 @@ PyTypeObject PyBufferedRWPair_Type = { 0, /* tp_iternext */ BufferedRWPair_methods, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + BufferedRWPair_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ |