Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ae4d5c6

Browse files
author
Tarek Ziadé
committed
Merged revisions 80830 via svnmerge from
svn+ssh://[email protected]/python/trunk ........ r80830 | tarek.ziade | 2010-05-06 00:15:31 +0200 (Thu, 06 May 2010) | 1 line Fixed #4265: shutil.copyfile() was leaking file descriptors when disk fills ........
1 parent 55f8ae2 commit ae4d5c6

3 files changed

Lines changed: 111 additions & 10 deletions

File tree

Lib/shutil.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,9 @@ def copyfile(src, dst):
9696
# XXX What about other special files? (sockets, devices...)
9797
if stat.S_ISFIFO(st.st_mode):
9898
raise SpecialFileError("`%s` is a named pipe" % fn)
99-
try:
100-
fsrc = open(src, 'rb')
101-
fdst = open(dst, 'wb')
102-
copyfileobj(fsrc, fdst)
103-
finally:
104-
if fdst:
105-
fdst.close()
106-
if fsrc:
107-
fsrc.close()
99+
with open(src, 'rb') as fsrc:
100+
with open(dst, 'wb') as fdst:
101+
copyfileobj(fsrc, fdst)
108102

109103
def copymode(src, dst):
110104
"""Copy mode bits from src to dst"""

Lib/test/test_shutil.py

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -798,8 +798,112 @@ def test_destinsrc_false_positive(self):
798798
shutil.rmtree(TESTFN, ignore_errors=True)
799799

800800

801+
class TestCopyFile(unittest.TestCase):
802+
803+
_delete = False
804+
805+
class Faux(object):
806+
_entered = False
807+
_exited_with = None
808+
_raised = False
809+
def __init__(self, raise_in_exit=False, suppress_at_exit=True):
810+
self._raise_in_exit = raise_in_exit
811+
self._suppress_at_exit = suppress_at_exit
812+
def read(self, *args):
813+
return ''
814+
def __enter__(self):
815+
self._entered = True
816+
def __exit__(self, exc_type, exc_val, exc_tb):
817+
self._exited_with = exc_type, exc_val, exc_tb
818+
if self._raise_in_exit:
819+
self._raised = True
820+
raise IOError("Cannot close")
821+
return self._suppress_at_exit
822+
823+
def tearDown(self):
824+
if self._delete:
825+
del shutil.open
826+
827+
def _set_shutil_open(self, func):
828+
shutil.open = func
829+
self._delete = True
830+
831+
def test_w_source_open_fails(self):
832+
def _open(filename, mode='r'):
833+
if filename == 'srcfile':
834+
raise IOError('Cannot open "srcfile"')
835+
assert 0 # shouldn't reach here.
836+
837+
self._set_shutil_open(_open)
838+
839+
self.assertRaises(IOError, shutil.copyfile, 'srcfile', 'destfile')
840+
841+
def test_w_dest_open_fails(self):
842+
843+
srcfile = self.Faux()
844+
845+
def _open(filename, mode='r'):
846+
if filename == 'srcfile':
847+
return srcfile
848+
if filename == 'destfile':
849+
raise IOError('Cannot open "destfile"')
850+
assert 0 # shouldn't reach here.
851+
852+
self._set_shutil_open(_open)
853+
854+
shutil.copyfile('srcfile', 'destfile')
855+
self.assertTrue(srcfile._entered)
856+
self.assertTrue(srcfile._exited_with[0] is IOError)
857+
self.assertEqual(srcfile._exited_with[1].args,
858+
('Cannot open "destfile"',))
859+
860+
def test_w_dest_close_fails(self):
861+
862+
srcfile = self.Faux()
863+
destfile = self.Faux(True)
864+
865+
def _open(filename, mode='r'):
866+
if filename == 'srcfile':
867+
return srcfile
868+
if filename == 'destfile':
869+
return destfile
870+
assert 0 # shouldn't reach here.
871+
872+
self._set_shutil_open(_open)
873+
874+
shutil.copyfile('srcfile', 'destfile')
875+
self.assertTrue(srcfile._entered)
876+
self.assertTrue(destfile._entered)
877+
self.assertTrue(destfile._raised)
878+
self.assertTrue(srcfile._exited_with[0] is IOError)
879+
self.assertEqual(srcfile._exited_with[1].args,
880+
('Cannot close',))
881+
882+
def test_w_source_close_fails(self):
883+
884+
srcfile = self.Faux(True)
885+
destfile = self.Faux()
886+
887+
def _open(filename, mode='r'):
888+
if filename == 'srcfile':
889+
return srcfile
890+
if filename == 'destfile':
891+
return destfile
892+
assert 0 # shouldn't reach here.
893+
894+
self._set_shutil_open(_open)
895+
896+
self.assertRaises(IOError,
897+
shutil.copyfile, 'srcfile', 'destfile')
898+
self.assertTrue(srcfile._entered)
899+
self.assertTrue(destfile._entered)
900+
self.assertFalse(destfile._raised)
901+
self.assertTrue(srcfile._exited_with[0] is None)
902+
self.assertTrue(srcfile._raised)
903+
904+
801905
def test_main():
802-
support.run_unittest(TestShutil, TestMove)
906+
support.run_unittest(TestShutil, TestMove, TestCopyFile)
803907

804908
if __name__ == '__main__':
805909
test_main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,9 @@ C-API
348348
Library
349349
-------
350350

351+
- Issue #4265: shutil.copyfile() was leaking file descriptors when disk fills.
352+
Patch by Tres Seaver.
353+
351354
- Issue #8390: tarfile uses surrogateespace as the default error handler
352355
(instead of replace in read mode or strict in write mode)
353356

0 commit comments

Comments
 (0)