Browse Source

separate closing from deferred closing for streams.. make context work...

don't leak file in SeekableArchive..
test_fixup
John-Mark Gurney 2 years ago
parent
commit
ba5832c545
3 changed files with 56 additions and 7 deletions
  1. BIN
      fixtures/testfile.tar.gz
  2. +9
    -7
      libarchive/__init__.py
  3. +47
    -0
      tests.py

BIN
fixtures/testfile.tar.gz View File


+ 9
- 7
libarchive/__init__.py View File

@@ -24,6 +24,7 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import os import os
import pathlib
import stat import stat
import sys import sys
import time import time
@@ -200,7 +201,7 @@ class EntryReadStream(object):
return self return self


def __exit__(self, *args): def __exit__(self, *args):
return
self.close()


def __iter__(self): def __iter__(self):
if self.closed: if self.closed:
@@ -438,17 +439,20 @@ class Archive(object):
self.encoding = encoding self.encoding = encoding
self.blocksize = blocksize self.blocksize = blocksize
self.password = password self.password = password
if isinstance(f, pathlib.PurePath):
f = str(f)
if isinstance(f, str): if isinstance(f, str):
self.filename = f self.filename = f
f = open(f, mode) f = open(f, mode)
# Only close it if we opened it... # Only close it if we opened it...
self._defer_close = True
self._doclose = True
elif hasattr(f, 'fileno'): elif hasattr(f, 'fileno'):
self.filename = getattr(f, 'name', None) self.filename = getattr(f, 'name', None)
# Leave the fd alone, caller should manage it... # Leave the fd alone, caller should manage it...
self._defer_close = False
self._doclose = False
else: else:
raise Exception('Provided file is not path or open file.') raise Exception('Provided file is not path or open file.')
self._defer_close = False
self.f = f self.f = f
self.mode = mode self.mode = mode
# Guess the format/filter from file name (if not provided) # Guess the format/filter from file name (if not provided)
@@ -493,7 +497,7 @@ class Archive(object):
return self return self


def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
self.denit()
self.close()


def __del__(self): def __del__(self):
self.close() self.close()
@@ -563,7 +567,7 @@ class Archive(object):
if hasattr(self.f, "fileno"): if hasattr(self.f, "fileno"):
os.fsync(self.f.fileno()) os.fsync(self.f.fileno())
# and then close it, if we opened it... # and then close it, if we opened it...
if getattr(self, '_close', None):
if self._doclose and getattr(self.f, 'close', None):
self.f.close() self.f.close()


@property @property
@@ -665,8 +669,6 @@ class SeekableArchive(Archive):
self._stream = None self._stream = None
# Convert file to open file. We need this to reopen the archive. # Convert file to open file. We need this to reopen the archive.
mode = kwargs.setdefault('mode', 'r') mode = kwargs.setdefault('mode', 'r')
if isinstance(f, str):
f = open(f, mode)
super(SeekableArchive, self).__init__(f, **kwargs) super(SeekableArchive, self).__init__(f, **kwargs)
self.entries = [] self.entries = []
self.eof = False self.eof = False


+ 47
- 0
tests.py View File

@@ -27,7 +27,9 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


import os, unittest, tempfile, random, string, sys import os, unittest, tempfile, random, string, sys
import hashlib
import io import io
import pathlib
import shutil import shutil
import zipfile import zipfile


@@ -368,6 +370,51 @@ class TestHighLevelAPI(unittest.TestCase, MakeTempMixIn):
with io.FileIO(zf.fileno(), mode='r', closefd=False) as f: with io.FileIO(zf.fileno(), mode='r', closefd=False) as f:
self._test_listing_content(f) self._test_listing_content(f)


_defaulthash = 'sha512'

def _readfp(fp):
while True:
r = fp.read(64*1024)
# libarchive returns None on EOF
if r == b'' or r is None:
return

yield r

def _hashfp(fp):
hash = getattr(hashlib, _defaulthash)()
for r in _readfp(fp):
hash.update(r)

return '%s:%s' % (_defaulthash, hash.hexdigest())


class TestArchive(unittest.TestCase):
def setUp(self):
self.fixtures = pathlib.Path(__file__).parent / 'fixtures'

def test_closed(self):
fname = self.fixtures / 'testfile.tar.gz'

with Archive(fname) as arch:
origfp = arch.f

hashes = []

for i in arch:
if not i.isfile():
continue

with arch.readstream(i.size) as fp:
hashes.append(_hashfp(fp))

self.assertTrue(fp.closed)
self.assertIsNone(arch._stream)

self.assertEqual(hashes, [ 'sha512:90f8342520f0ac57fb5a779f5d331c2fa87aa40f8799940257f9ba619940951e67143a8d746535ed0284924b2b7bc1478f095198800ba96d01847d7b56ca465c', 'sha512:7d5768d47b6bc27dc4fa7e9732cfa2de506ca262a2749cb108923e5dddffde842bbfee6cb8d692fb43aca0f12946c521cce2633887914ca1f96898478d10ad3f' ])

self.assertTrue(arch.f.closed)



if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

Loading…
Cancel
Save