Commit a3712a9a authored by Serhiy Storchaka's avatar Serhiy Storchaka

Issue #5700: io.FileIO() called flush() after closing the file.

flush() was not called in close() if closefd=False.
parent 5e3d7a40
...@@ -593,13 +593,43 @@ class IOTest(unittest.TestCase): ...@@ -593,13 +593,43 @@ class IOTest(unittest.TestCase):
with self.open(zero, "r") as f: with self.open(zero, "r") as f:
self.assertRaises(OverflowError, f.read) self.assertRaises(OverflowError, f.read)
def test_flush_error_on_close(self): def check_flush_error_on_close(self, *args, **kwargs):
f = self.open(support.TESTFN, "wb", buffering=0) # 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(): def bad_flush():
closed[:] = [f.closed]
raise OSError() raise OSError()
f.flush = bad_flush f.flush = bad_flush
self.assertRaises(OSError, f.close) # exception not swallowed self.assertRaises(OSError, f.close) # exception not swallowed
self.assertTrue(f.closed) 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): def test_multi_close(self):
f = self.open(support.TESTFN, "wb", buffering=0) f = self.open(support.TESTFN, "wb", buffering=0)
...@@ -788,13 +818,21 @@ class CommonBufferedTests: ...@@ -788,13 +818,21 @@ class CommonBufferedTests:
self.assertEqual(repr(b), "<%s name=b'dummy'>" % clsname) self.assertEqual(repr(b), "<%s name=b'dummy'>" % clsname)
def test_flush_error_on_close(self): 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() raw = self.MockRawIO()
closed = []
def bad_flush(): def bad_flush():
closed[:] = [b.closed, raw.closed]
raise OSError() raise OSError()
raw.flush = bad_flush raw.flush = bad_flush
b = self.tp(raw) b = self.tp(raw)
self.assertRaises(OSError, b.close) # exception not swallowed self.assertRaises(OSError, b.close) # exception not swallowed
self.assertTrue(b.closed) 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): def test_close_error_on_close(self):
raw = self.MockRawIO() raw = self.MockRawIO()
...@@ -2618,12 +2656,20 @@ class TextIOWrapperTest(unittest.TestCase): ...@@ -2618,12 +2656,20 @@ class TextIOWrapperTest(unittest.TestCase):
self.assertEqual(content.count("Thread%03d\n" % n), 1) self.assertEqual(content.count("Thread%03d\n" % n), 1)
def test_flush_error_on_close(self): 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") txt = self.TextIOWrapper(self.BytesIO(self.testdata), encoding="ascii")
closed = []
def bad_flush(): def bad_flush():
closed[:] = [txt.closed, txt.buffer.closed]
raise OSError() raise OSError()
txt.flush = bad_flush txt.flush = bad_flush
self.assertRaises(OSError, txt.close) # exception not swallowed self.assertRaises(OSError, txt.close) # exception not swallowed
self.assertTrue(txt.closed) 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): def test_close_error_on_close(self):
buffer = self.BytesIO(self.testdata) buffer = self.BytesIO(self.testdata)
......
...@@ -13,6 +13,9 @@ Core and Builtins ...@@ -13,6 +13,9 @@ Core and Builtins
Library 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 - Issue #23374: Fixed pydoc failure with non-ASCII files when stdout encoding
differs from file system encoding (e.g. on Mac OS). differs from file system encoding (e.g. on Mac OS).
......
...@@ -126,11 +126,18 @@ internal_close(fileio *self) ...@@ -126,11 +126,18 @@ internal_close(fileio *self)
static PyObject * static PyObject *
fileio_close(fileio *self) fileio_close(fileio *self)
{ {
PyObject *res;
PyObject *exc, *val, *tb;
int rc;
_Py_IDENTIFIER(close); _Py_IDENTIFIER(close);
res = _PyObject_CallMethodId((PyObject*)&PyRawIOBase_Type,
&PyId_close, "O", self);
if (!self->closefd) { if (!self->closefd) {
self->fd = -1; self->fd = -1;
Py_RETURN_NONE; return res;
} }
if (res == NULL)
PyErr_Fetch(&exc, &val, &tb);
if (self->finalizing) { if (self->finalizing) {
PyObject *r = fileio_dealloc_warn(self, (PyObject *) self); PyObject *r = fileio_dealloc_warn(self, (PyObject *) self);
if (r) if (r)
...@@ -138,12 +145,12 @@ fileio_close(fileio *self) ...@@ -138,12 +145,12 @@ fileio_close(fileio *self)
else else
PyErr_Clear(); PyErr_Clear();
} }
errno = internal_close(self); rc = internal_close(self);
if (errno < 0) if (res == NULL)
return NULL; _PyErr_ChainExceptions(exc, val, tb);
if (rc < 0)
return _PyObject_CallMethodId((PyObject*)&PyRawIOBase_Type, Py_CLEAR(res);
&PyId_close, "O", self); return res;
} }
static PyObject * static PyObject *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment