summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apt/cache.py23
-rw-r--r--debian/changelog10
-rw-r--r--debian/control1
-rw-r--r--debian/tests/control2
-rw-r--r--debian/tests/run-tests8
-rw-r--r--tests/test_apt_cache.py57
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")