]> granicus.if.org Git - python/commitdiff
Issue #5700: io.FileIO() called flush() after closing the file.
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 20 Feb 2015 22:35:09 +0000 (00:35 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Fri, 20 Feb 2015 22:35:09 +0000 (00:35 +0200)
flush() was not called in close() if closefd=False.

Lib/test/test_io.py
Misc/NEWS
Modules/_io/fileio.c

index a424f760f7a2a057a38503e69c30cbe0fcd0a869..79cd87b45ff8c745e5e512a4e1ff40544ff603df 100644 (file)
@@ -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)
index 83518d24dd3e1bfbc584629a2ee04514fe6e4ae3..bcabefaf3f9537a54c839b38a54a112d3981e117 100644 (file)
--- 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).
 
index a2b253bd03b16b478b400f51fef6e6fc349f9c64..80ca99c5bcdc6c0a5cdbc4aef8d8baf5a54cb64f 100644 (file)
@@ -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 *