summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2015-02-21 00:35:09 +0200
committerSerhiy Storchaka <storchaka@gmail.com>2015-02-21 00:35:09 +0200
commite0c33606506b8475e3d396a83d47fc3f1ebce893 (patch)
treecaa00832b7beb2f5bda7601f55999de5bcd5c4d7
parent66dbd68ea7492d3191d9bd01e23b0081db7bfe98 (diff)
downloadcpython-e0c33606506b8475e3d396a83d47fc3f1ebce893.tar.gz
Issue #5700: io.FileIO() called flush() after closing the file.
flush() was not called in close() if closefd=False.
-rw-r--r--Lib/test/test_io.py50
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_io/fileio.c21
3 files changed, 65 insertions, 9 deletions
diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py
index a424f760f7..79cd87b45f 100644
--- a/Lib/test/test_io.py
+++ b/Lib/test/test_io.py
@@ -593,13 +593,43 @@ class IOTest(unittest.TestCase):
with self.open(zero, "r") as f:
self.assertRaises(OverflowError, f.read)
- def test_flush_error_on_close(self):
- f = self.open(support.TESTFN, "wb", buffering=0)
+ def check_flush_error_on_close(self, *args, **kwargs):
+ # Test that the file is closed despite failed flush
+ # and that flush() is called before file closed.
+ f = self.open(*args, **kwargs)
+ closed = []
def bad_flush():
+ closed[:] = [f.closed]
raise OSError()
f.flush = bad_flush
self.assertRaises(OSError, f.close) # exception not swallowed
self.assertTrue(f.closed)
+ self.assertTrue(closed) # flush() called
+ self.assertFalse(closed[0]) # flush() called before file closed
+
+ def test_flush_error_on_close(self):
+ # raw file
+ # Issue #5700: io.FileIO calls flush() after file closed
+ self.check_flush_error_on_close(support.TESTFN, 'wb', buffering=0)
+ fd = os.open(support.TESTFN, os.O_WRONLY|os.O_CREAT)
+ self.check_flush_error_on_close(fd, 'wb', buffering=0)
+ fd = os.open(support.TESTFN, os.O_WRONLY|os.O_CREAT)
+ self.check_flush_error_on_close(fd, 'wb', buffering=0, closefd=False)
+ os.close(fd)
+ # buffered io
+ self.check_flush_error_on_close(support.TESTFN, 'wb')
+ fd = os.open(support.TESTFN, os.O_WRONLY|os.O_CREAT)
+ self.check_flush_error_on_close(fd, 'wb')
+ fd = os.open(support.TESTFN, os.O_WRONLY|os.O_CREAT)
+ self.check_flush_error_on_close(fd, 'wb', closefd=False)
+ os.close(fd)
+ # text io
+ self.check_flush_error_on_close(support.TESTFN, 'w')
+ fd = os.open(support.TESTFN, os.O_WRONLY|os.O_CREAT)
+ self.check_flush_error_on_close(fd, 'w')
+ fd = os.open(support.TESTFN, os.O_WRONLY|os.O_CREAT)
+ self.check_flush_error_on_close(fd, 'w', closefd=False)
+ os.close(fd)
def test_multi_close(self):
f = self.open(support.TESTFN, "wb", buffering=0)
@@ -788,13 +818,21 @@ class CommonBufferedTests:
self.assertEqual(repr(b), "<%s name=b'dummy'>" % clsname)
def test_flush_error_on_close(self):
+ # Test that buffered file is closed despite failed flush
+ # and that flush() is called before file closed.
raw = self.MockRawIO()
+ closed = []
def bad_flush():
+ closed[:] = [b.closed, raw.closed]
raise OSError()
raw.flush = bad_flush
b = self.tp(raw)
self.assertRaises(OSError, b.close) # exception not swallowed
self.assertTrue(b.closed)
+ self.assertTrue(raw.closed)
+ self.assertTrue(closed) # flush() called
+ self.assertFalse(closed[0]) # flush() called before file closed
+ self.assertFalse(closed[1])
def test_close_error_on_close(self):
raw = self.MockRawIO()
@@ -2618,12 +2656,20 @@ class TextIOWrapperTest(unittest.TestCase):
self.assertEqual(content.count("Thread%03d\n" % n), 1)
def test_flush_error_on_close(self):
+ # Test that text file is closed despite failed flush
+ # and that flush() is called before file closed.
txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii")
+ closed = []
def bad_flush():
+ closed[:] = [txt.closed, txt.buffer.closed]
raise OSError()
txt.flush = bad_flush
self.assertRaises(OSError, txt.close) # exception not swallowed
self.assertTrue(txt.closed)
+ self.assertTrue(txt.buffer.closed)
+ self.assertTrue(closed) # flush() called
+ self.assertFalse(closed[0]) # flush() called before file closed
+ self.assertFalse(closed[1])
def test_close_error_on_close(self):
buffer = self.BytesIO(self.testdata)
diff --git a/Misc/NEWS b/Misc/NEWS
index 83518d24dd..bcabefaf3f 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,9 @@ Core and Builtins
Library
-------
+- Issue #5700: io.FileIO() called flush() after closing the file.
+ flush() was not called in close() if closefd=False.
+
- Issue #23374: Fixed pydoc failure with non-ASCII files when stdout encoding
differs from file system encoding (e.g. on Mac OS).
diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c
index a2b253bd03..80ca99c5bc 100644
--- a/Modules/_io/fileio.c
+++ b/Modules/_io/fileio.c
@@ -126,11 +126,18 @@ internal_close(fileio *self)
static PyObject *
fileio_close(fileio *self)
{
+ PyObject *res;
+ PyObject *exc, *val, *tb;
+ int rc;
_Py_IDENTIFIER(close);
+ res = _PyObject_CallMethodId((PyObject*)&PyRawIOBase_Type,
+ &PyId_close, "O", self);
if (!self->closefd) {
self->fd = -1;
- Py_RETURN_NONE;
+ return res;
}
+ if (res == NULL)
+ PyErr_Fetch(&exc, &val, &tb);
if (self->finalizing) {
PyObject *r = fileio_dealloc_warn(self, (PyObject *) self);
if (r)
@@ -138,12 +145,12 @@ fileio_close(fileio *self)
else
PyErr_Clear();
}
- errno = internal_close(self);
- if (errno < 0)
- return NULL;
-
- return _PyObject_CallMethodId((PyObject*)&PyRawIOBase_Type,
- &PyId_close, "O", self);
+ rc = internal_close(self);
+ if (res == NULL)
+ _PyErr_ChainExceptions(exc, val, tb);
+ if (rc < 0)
+ Py_CLEAR(res);
+ return res;
}
static PyObject *