diff options
| -rw-r--r-- | apt/cache.py | 23 | ||||
| -rw-r--r-- | debian/changelog | 10 | ||||
| -rw-r--r-- | debian/control | 1 | ||||
| -rw-r--r-- | debian/tests/control | 2 | ||||
| -rw-r--r-- | debian/tests/run-tests | 8 | ||||
| -rw-r--r-- | tests/test_apt_cache.py | 57 |
6 files changed, 93 insertions, 8 deletions
diff --git a/apt/cache.py b/apt/cache.py index 86a788bb..9842cb2a 100644 --- a/apt/cache.py +++ b/apt/cache.py @@ -42,6 +42,9 @@ class FetchFailedException(IOError): class LockFailedException(IOError): """Exception that is thrown when locking fails.""" +class CacheClosedException(Exception): + """Exception that is thrown when the cache is used after close().""" + class Cache(object): """Dictionary-like package cache. @@ -139,6 +142,8 @@ class Cache(object): """ if progress is None: progress = apt.progress.base.OpProgress() + # close old cache on (re)open + self.close() self.op_progress = progress self._run_callbacks("cache_pre_open") @@ -172,6 +177,20 @@ class Cache(object): progress.done() self._run_callbacks("cache_post_open") + def close(self): + """ Close the package cache """ + # explicitely free the FDs that _records has open + del self._records + self._records = None + + def __enter__(self): + """ Enter the with statement """ + return self + + def __exit__(self, exc_type, exc_value, traceback): + """ Exit the with statement """ + self.close() + def __getitem__(self, key): """ look like a dictionary (get key) """ try: @@ -238,6 +257,8 @@ class Cache(object): @property def required_download(self): """Get the size of the packages that are required to download.""" + if self._records is None: + raise CacheClosedException("Cache object used after close() called") pm = apt_pkg.PackageManager(self._depcache) fetcher = apt_pkg.Acquire() pm.get_archives(fetcher, self._list, self._records) @@ -288,6 +309,8 @@ class Cache(object): def _fetch_archives(self, fetcher, pm): """ fetch the needed archives """ + if self._records is None: + raise CacheClosedException("Cache object used after close() called") # get lock lockfile = apt_pkg.config.find_dir("Dir::Cache::Archives") + "lock" diff --git a/debian/changelog b/debian/changelog index 7a748e6f..8a7e3392 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,16 @@ -python-apt (0.8.8.1) UNRELEASED; urgency=low +python-apt (0.8.8.1~exp1) UNRELEASED; urgency=low + [ Michael Vogt ] * python/tag.cc: - make TagSecString_FromStringAndSize, TagSecString_FromString static, thanks to jcristau * python/cache.cc: - add "Codename" to PackageFile object + * add dep8 style autopkgtest support + + [ Jason Conti ] + * lp:~jconti/python-apt/closeable-cache: + - add apt.Cache.close() method -- Michael Vogt <michael.vogt@ubuntu.com> Mon, 15 Oct 2012 10:03:21 +0200 @@ -45,7 +51,7 @@ python-apt (0.8.8) unstable; urgency=low value rather than a random one. * lp:~jamesodhunt/python-apt/test-for-size_to_str: - add test for size_to_str() to help with finding LP: #1030278 - + -- Michael Vogt <mvo@debian.org> Fri, 12 Oct 2012 10:47:11 +0200 python-apt (0.8.7) unstable; urgency=low diff --git a/debian/control b/debian/control index 1619aacb..dd951ed2 100644 --- a/debian/control +++ b/debian/control @@ -21,6 +21,7 @@ Build-Depends: apt (>= 0.9.6), python-unittest2 Vcs-Bzr: http://bzr.debian.org/apt/python-apt/debian-sid Vcs-Browser: http://bzr.debian.org/loggerhead/apt/python-apt/debian-sid/changes +XS-Testsuite: autopkgtest Package: python-apt Architecture: any diff --git a/debian/tests/control b/debian/tests/control new file mode 100644 index 00000000..2ca0a401 --- /dev/null +++ b/debian/tests/control @@ -0,0 +1,2 @@ +Tests: run-tests +Depends: @, apt-utils, python-debian, fakeroot, intltool diff --git a/debian/tests/run-tests b/debian/tests/run-tests new file mode 100644 index 00000000..bb980c6b --- /dev/null +++ b/debian/tests/run-tests @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +# from debian/rules +for python in $(utils/pyversions -r); do + $python tests/test_all.py -q +done diff --git a/tests/test_apt_cache.py b/tests/test_apt_cache.py index 448aed75..7e2ead2d 100644 --- a/tests/test_apt_cache.py +++ b/tests/test_apt_cache.py @@ -7,19 +7,27 @@ # are permitted in any medium without royalty provided the copyright # notice and this notice are preserved. """Unit tests for verifying the correctness of check_dep, etc in apt_pkg.""" + +import glob +import logging import os +import shutil +import sys import tempfile import unittest +if sys.version_info[0] == 2 and sys.version_info[1] == 6: + from unittest2 import TestCase +else: + from unittest import TestCase + + from test_all import get_library_dir -import sys sys.path.insert(0, get_library_dir()) import apt import apt_pkg -import shutil -import glob -import logging + def if_sources_list_is_readable(f): def wrapper(*args, **kwargs): @@ -29,7 +37,17 @@ def if_sources_list_is_readable(f): logging.warn("skipping '%s' because sources.list is not readable" % f) return wrapper -class TestAptCache(unittest.TestCase): + +def get_open_file_descriptors(): + try: + fds = os.listdir("/proc/self/fd") + except OSError: + logging.warn("failed to list /proc/self/fd") + return set([]) + return set(map(int, fds)) + + +class TestAptCache(TestCase): """ test the apt cache """ def setUp(self): @@ -68,7 +86,34 @@ class TestAptCache(unittest.TestCase): self.assertEqual(r['Package'], pkg.shortname) self.assertTrue('Version' in r) self.assertTrue(len(r['Description']) > 0) - self.assertTrue(str(r).startswith('Package: %s\n' % pkg.shortname)) + self.assertTrue( + str(r).startswith('Package: %s\n' % pkg.shortname)) + + @if_sources_list_is_readable + def test_cache_close_leak_fd(self): + fds_before_open = get_open_file_descriptors() + cache = apt.Cache() + opened_fd = get_open_file_descriptors().difference(fds_before_open) + cache.close() + fds_after_close = get_open_file_descriptors() + unclosed_fd = opened_fd.intersection(fds_after_close) + self.assertEqual(fds_before_open, fds_after_close) + self.assertEqual(unclosed_fd, set()) + + def test_cache_open_twice_leaks_fds(self): + cache = apt.Cache() + fds_before_open = get_open_file_descriptors() + cache.open() + fds_after_open_twice = get_open_file_descriptors() + self.assertEqual(fds_before_open, fds_after_open_twice) + + @if_sources_list_is_readable + def test_cache_close_download_fails(self): + cache = apt.Cache() + self.assertEqual(cache.required_download, 0) + cache.close() + with self.assertRaises(apt.cache.CacheClosedException): + cache.required_download def test_get_provided_packages(self): apt.apt_pkg.config.set("Apt::architecture", "i386") |
