Merge lp:~jml/pkgme-devportal/restore-download-file into lp:pkgme-devportal

Proposed by Jonathan Lange
Status: Merged
Approved by: Jonathan Lange
Approved revision: 180
Merged at revision: 146
Proposed branch: lp:~jml/pkgme-devportal/restore-download-file
Merge into: lp:pkgme-devportal
Diff against target: 798 lines (+15/-665)
7 files modified
NEWS (+3/-0)
devportalbinary/aptfile.py (+0/-226)
devportalbinary/database.py (+5/-131)
devportalbinary/testing.py (+3/-139)
devportalbinary/tests/test_aptfile.py (+0/-53)
devportalbinary/tests/test_database.py (+2/-114)
setup.py (+2/-2)
To merge this branch: bzr merge lp:~jml/pkgme-devportal/restore-download-file
Reviewer Review Type Date Requested Status
Jonathan Lange Approve
Review via email: mp+134116@code.launchpad.net

Commit message

Add download_file back to devportalbinary.database

Description of the change

download_file was being imported by pkgme-service from devportalbinary.database.

Another branch is in progress, removing this particular dependency. In the mean
time, this branch should land, restoring the behaviour so we can bump pkgme-service
to use latest devportal without probs.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Rubberstamp! Proposer approves of own proposal.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2012-11-13 11:25:44 +0000
+++ NEWS 2012-11-13 14:53:23 +0000
@@ -12,6 +12,9 @@
12 maintain a database of library dependencies based on published packages12 maintain a database of library dependencies based on published packages
13 from Launchpad has been removed. (Jonathan Lange)13 from Launchpad has been removed. (Jonathan Lange)
1414
15 * ``aptfile`` backend has been removed. (Jonathan Lange)
16
17
150.4.11 (2012-10-29)180.4.11 (2012-10-29)
16===================19===================
1720
1821
=== removed file 'devportalbinary/aptfile.py'
--- devportalbinary/aptfile.py 2012-11-13 11:25:44 +0000
+++ devportalbinary/aptfile.py 1970-01-01 00:00:00 +0000
@@ -1,226 +0,0 @@
1# Copyright 2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4# XXX: Remove this. No longer used.
5
6__all__ = [
7 'AptFilePackageDatabase',
8 ]
9
10import gzip
11import urllib
12import argparse
13import os
14import re
15
16
17def make_arg_parser():
18 p = argparse.ArgumentParser()
19 p.add_argument('--cache-dir', type=str, default='cache')
20 p.add_argument('output_file', type=argparse.FileType('w'))
21 return p
22
23
24so_filename_re = re.compile(r'\.so(\.[0-9]+)*$')
25def export_database(db, stream):
26 for library, package, arch in db.iter_database():
27 if so_filename_re.search(library):
28 stream.write(','.join([package, library, package, arch]))
29 stream.write('\n')
30 stream.flush()
31
32
33def dump_apt_file_db():
34 parser = make_arg_parser()
35 args = parser.parse_args()
36 if not os.path.isdir(args.cache_dir):
37 os.path.makedirs(args.cache_dir)
38 db = AptFilePackageDatabase(args.cache_dir)
39 export_database(db, args.output_file)
40 return 0
41
42
43def iter_contents_file(contents):
44 """ Yield (full-library-path, set-of-pkgnames) from a Contents file.
45
46 It expects a line starting with "FILE" that tells it when the header ends
47 and the actual content starts.
48 """
49 found_start_marker = False
50 for line in contents:
51 if not found_start_marker:
52 if line.startswith("FILE"):
53 found_start_marker = True
54 continue
55 (path, sep, pkgs) = [s.strip() for s in line.rpartition(" ")]
56 # pkgs is formated a bit funny, e.g. universe/pkgname
57 pkgs = set([os.path.basename(pkg) for pkg in pkgs.split(",")])
58 yield (path, pkgs)
59
60
61class AptFilePackageDatabase(object):
62 """Really dumb database that just uses apt-file for local testing """
63
64 # we could also read /etc/ld.so.conf.d/*.conf but this maybe different on
65 # different distroseries especially if
66 # server-distroseries != target-distroseries
67 # (I wish there was ldconfig --print-search-dirs)
68 LD_SEARCH_PATH = [
69 # standards
70 "lib",
71 "usr/lib",
72 "usr/local/lib",
73 # old biarch
74 "lib32",
75 "usr/lib32",
76 # new multiarch
77 "lib/i686-linux-gnu",
78 "lib/i386-linux-gnu",
79 "lib/x86_64-linux-gnu",
80 "usr/lib/i386-linux-gnu",
81 "usr/lib/i686-linux-gnu",
82 "usr/lib/x86_64-linux-gnu",
83 # ?
84 "usr/lib/x86_64-linux-gnu/fakechroot",
85 "usr/lib/x86_64-linux-gnu/mesa",
86 "usr/lib/x86_64-linux-gnu/mesa-egl",
87 "usr/lib/i386-linux-gnu/mesa",
88 ]
89
90 DISTROSERIES = "oneiric"
91
92 # If db_type is set to this in the config, that means use this database.
93 DB_TYPE = 'aptfile'
94
95 CONTENTS_FILE_URL_LOCATION = (
96 "http://archive.ubuntu.com/ubuntu/dists/%(distroseries)s/"
97 "Contents-%(arch)s.gz")
98
99 CONTENTS_FILE = "Contents-%(distroseries)s-%(arch)s"
100
101 def __init__(self, cachedir):
102 self.cachedir = os.path.expanduser(cachedir)
103 self._distroseries_arch_cache = {}
104
105 @classmethod
106 def from_options(cls, options):
107 return cls(options.database_aptfile_cachedir)
108
109 def _get_lib_to_pkgs_mapping(self, distroseries, arch):
110 """Returns a dict of { library-name : set([pkg1,pkg2])
111
112 This function will return a dict to lookup library-name to package
113 dependencies for the given distroseries and architecture
114 """
115 if not (distroseries, arch) in self._distroseries_arch_cache:
116 self._distroseries_arch_cache[(distroseries, arch)] = \
117 self._get_mapping_from_contents_file(distroseries, arch)
118 return self._distroseries_arch_cache[(distroseries, arch)]
119
120 def _get_contents_file_cache_path(self, distroseries, arch):
121 """Return the path in the cache for the given distroseries, arch """
122 return os.path.join(
123 self.cachedir, self.CONTENTS_FILE % {
124 'distroseries': distroseries, 'arch': arch})
125
126 def _get_contents_file_server_url(self, distroseries, arch):
127 """Return the remote server URL for the given distroseries, arch """
128 return self.CONTENTS_FILE_URL_LOCATION % {
129 'distroseries': distroseries, 'arch': arch}
130
131 def _get_mapping_from_contents_file(self, distroseries, arch):
132 """Return lib,pkgs mapping from contents file for distroseries, arch
133
134 This expects the contents file to be in the cachedir already.
135 """
136 lib_to_pkgs = {}
137 path = self._get_contents_file_cache_path(distroseries, arch)
138 with open(path) as f:
139 for path, pkgs in self._iter_contents_file(f):
140 basename = os.path.basename(path)
141 if not basename in lib_to_pkgs:
142 lib_to_pkgs[basename] = set()
143 lib_to_pkgs[basename] |= pkgs
144 return lib_to_pkgs
145
146 def _download_contents_file_compressed(self, distroseries, arch):
147 """Downloads the content file for distroseries, arch into target """
148 # XXX: we may eventually want to merge the Contents files from
149 # the -updates repository too in addition to the main archive
150 url = self._get_contents_file_server_url(distroseries, arch)
151 target = self._get_contents_file_cache_path(distroseries, arch)
152 compressed_target = target + os.path.splitext(url)[1]
153 # download
154 urllib.urlretrieve(url, compressed_target)
155 return compressed_target
156
157 def _iter_contents_file(self, in_file):
158 for path, pkgs in iter_contents_file(in_file):
159 if os.path.dirname(path) in self.LD_SEARCH_PATH:
160 yield path, pkgs
161
162 def _prune_contents_gz_file(self, infile, outfile):
163 """Read a compressed Contents.gz and write out a pruned version.
164
165 This will use iter_contents_file to go over infile and write
166 the relevant lines that are in the LD_SEARCH_PATH to outfile.
167 """
168 with open(outfile, "w") as outf, gzip.open(infile) as inf:
169 # first write the header
170 outf.write("FILE LOCATION\n")
171 # then iter over all relevant lines and write them out
172 for path, pkgs in self._iter_contents_file(inf):
173 outf.write("%s %s\n" % (path, ",".join(pkgs)))
174
175 def _download_and_prepare_contents_file_if_needed(self, distroseries, arch):
176 """Ensure there is a usable Contents file in the cachedir
177
178 This will download, uncompress and prune a Conents file for
179 distroseries, arch so that get_dependencies works.
180 """
181 # mvo: We can (and should eventually) do etag/if-modified-since
182 # matching here. But its not really important as long as
183 # we package for stable distroseries as the Contents file
184 # will not change
185 path = self._get_contents_file_cache_path(distroseries, arch)
186 if not os.path.exists(path):
187 compressed_contents = self._download_contents_file_compressed(
188 distroseries, arch)
189 # and prune from ~300mb to 1mb uncompressed as we are only
190 # interested in the library path parts
191 self._prune_contents_gz_file(compressed_contents, path)
192 os.remove(compressed_contents)
193
194 def iter_database(self, architectures=('i386', 'amd64'),
195 distroseries=None):
196 """Export the database.
197
198 Yields (library, package, arch) tuples for everything that we can
199 find.
200 """
201 # XXX: Untested
202 if distroseries is None:
203 distroseries = self.DISTROSERIES
204 for arch in architectures:
205 self._download_and_prepare_contents_file_if_needed(
206 distroseries, arch)
207 mapping = self._get_lib_to_pkgs_mapping(distroseries, arch)
208 for library in mapping:
209 for package in mapping[library]:
210 yield library, package, arch
211
212 def get_multiple_dependencies(self, library_names, arch):
213 """Get the binary packages that provide libraries.
214
215 :return: (deps, missing), where ``deps`` is a dict mapping library
216 names to sets of packages that provide them, and ``missing`` is a
217 set of library names for which no dependencies could be found.
218 """
219 self._download_and_prepare_contents_file_if_needed(
220 self.DISTROSERIES, arch)
221 lib_to_pkgs = self._get_lib_to_pkgs_mapping(self.DISTROSERIES, arch)
222 deps = ((lib, lib_to_pkgs.get(lib)) for lib in library_names)
223 return dict((lib, dep) for (lib, dep) in deps if dep)
224
225 def close(self):
226 pass
2270
=== modified file 'devportalbinary/database.py'
--- devportalbinary/database.py 2012-11-13 11:28:53 +0000
+++ devportalbinary/database.py 2012-11-13 14:53:23 +0000
@@ -1,134 +1,13 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the1# Copyright 2011 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from storm.expr import And, Column, Select, Table4from .configuration import load_configuration
5from storm.locals import create_database, Store5from .utils import download_file
6from storm.uri import URI as StormURI
7
8from .aptfile import AptFilePackageDatabase
9from .configuration import (
10 CONF_FILE_ENV_VAR,
11 get_config_file_path,
12 load_configuration,
13 )
146
15from libdep_service_client.client import Client7from libdep_service_client.client import Client
168
179# Shut up pyflakes. Imported because other things use this.
18class URI(StormURI):10download_file
19 """A stand-in for Storm's URI class.
20
21 This class implements the same interface as `storm.uri.URI`, except
22 that the constructor has a different signature. Storm's version takes
23 a string and parses it, this version can be used when you already
24 have a parsed version and just need to create the object.
25 """
26
27 # XXX: Only used by PackageDatabase, which is flagged for deletion.
28
29 def __init__(self, scheme=None, host=None, port=None, username=None,
30 password=None, database=None, options=None):
31 self.scheme = scheme
32 self.host = host
33 self.port = port
34 self.username = username
35 self.password = password
36 self.database = database
37 self.options = options
38 if self.options is None:
39 self.options = dict()
40
41
42class PackageDatabase(object):
43
44 # XXX: No longer used within pkgme-devportal
45
46 SQLITE = 'sqlite'
47 POSTGRES = 'postgres'
48
49 def __init__(self, store):
50 self._store = store
51
52 @classmethod
53 def _get_storm_sqlite_connection_uri(cls, opts):
54 raise ValueError(
55 "SQLite is no longer supported, you must migrate to postgresql.")
56
57 @classmethod
58 def _get_storm_postgres_connection_uri(cls, opts):
59 if not getattr(opts, 'database_db_name', None):
60 raise ValueError(
61 "Can't create database, no connection info available. "
62 "You must specify %s. Looked in %s. "
63 "Perhaps %s is set incorrectly?" % (
64 'db_name', get_config_file_path(), CONF_FILE_ENV_VAR))
65 return URI(scheme=opts.database_db_type,
66 username=opts.database_username,
67 password=opts.database_password,
68 host=opts.database_host,
69 port=opts.database_port,
70 database=opts.database_db_name)
71
72 @classmethod
73 def _get_storm_connection_uri(cls, opts):
74 if opts.database_db_type == cls.POSTGRES:
75 return cls._get_storm_postgres_connection_uri(opts)
76 elif opts.database_db_type == cls.SQLITE:
77 return cls._get_storm_sqlite_connection_uri(opts)
78 else:
79 raise AssertionError(
80 "Unsupported database: %s" % opts.database_db_type)
81
82 @classmethod
83 def get_db_info_from_config(cls, opts):
84 return cls._get_storm_connection_uri(opts)
85
86 @classmethod
87 def get_store_from_config(cls, opts):
88 """Create a storm store based on a config file.
89
90 This method will create a storm store based
91 on the information in ``~/.config/pkgme-binary/conf``
92
93 :return: a tuple of (store, store_type), where store_type
94 is one of cls.SQLITE or cls.POSTGRES, indicating what
95 is at the other end of the store.
96 """
97 connection_info = cls.get_db_info_from_config(opts)
98 database = create_database(connection_info)
99 return Store(database)
100
101 @classmethod
102 def from_options(cls, options):
103 return cls(cls.get_store_from_config(options))
104
105 def _get_query(self, library_names, arch):
106 return Select(
107 [Column('library'), Column('dependency')],
108 And(Column('architecture') == arch,
109 Column('library').is_in(map(unicode, library_names))),
110 Table('libdep'))
111
112 def get_multiple_dependencies(self, library_names, arch):
113 """Get the binary packages that provide libraries.
114
115 :return: (deps, missing), where ``deps`` is a dict mapping library
116 names to sets of packages that provide them, and ``missing`` is a
117 set of library names for which no dependencies could be found.
118 """
119 arch = unicode(arch)
120 result = self._store.execute(self._get_query(library_names, arch))
121 found = {}
122 for row in result:
123 [lib, dependency] = row
124 if lib in found:
125 found[lib].add(dependency)
126 else:
127 found[lib] = set([dependency])
128 return found
129
130 def close(self):
131 self._store.close()
13211
13312
134class LibdepServiceClient(object):13class LibdepServiceClient(object):
@@ -159,10 +38,5 @@
15938
160def get_dependency_database():39def get_dependency_database():
161 """Return an object that can get dependencies."""40 """Return an object that can get dependencies."""
162 # XXX: Remove AptFilePackageDatabase from here and simplify the method.
163 databases = {
164 AptFilePackageDatabase.DB_TYPE: AptFilePackageDatabase.from_options,
165 LibdepServiceClient.DB_TYPE: LibdepServiceClient.from_options,
166 }
167 options = load_configuration()41 options = load_configuration()
168 return databases[options.database_db_type](options)42 return LibdepServiceClient.from_options(options)
16943
=== modified file 'devportalbinary/testing.py'
--- devportalbinary/testing.py 2012-11-13 11:12:08 +0000
+++ devportalbinary/testing.py 2012-11-13 14:53:23 +0000
@@ -1,7 +1,6 @@
1# Copyright 2011-2012 Canonical Ltd. This software is licensed under the1# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4from contextlib import closing
5import json4import json
6import os5import os
7import random6import random
@@ -21,11 +20,6 @@
21 Fixture,20 Fixture,
22 TempDir,21 TempDir,
23 )22 )
24from postgresfixture import ClusterFixture
25from storm.locals import create_database, Store
26from testresources import (
27 FixtureResource as _FixtureResource,
28 )
29from testtools import TestCase23from testtools import TestCase
30from treeshape import (24from treeshape import (
31 from_rough_spec,25 from_rough_spec,
@@ -33,11 +27,7 @@
33 )27 )
3428
35from devportalbinary.binary import MetadataBackend29from devportalbinary.binary import MetadataBackend
36from devportalbinary.database import (30from devportalbinary.database import LibdepServiceClient
37 LibdepServiceClient,
38 PackageDatabase,
39 URI,
40 )
4131
42from devportalbinary.configuration import CONF_FILE_ENV_VAR32from devportalbinary.configuration import CONF_FILE_ENV_VAR
4333
@@ -90,133 +80,6 @@
90 im.size[0], im.size[1], im.format))80 im.size[0], im.size[1], im.format))
9181
9282
93def get_db_schema_file_path(name):
94 return os.path.join(os.path.dirname(
95 os.path.abspath(__file__)), 'db', name)
96
97
98def get_db_schema_queries(filenames):
99 for filename in filenames:
100 path = get_db_schema_file_path(filename)
101 with open(path) as f:
102 yield f.read()
103
104
105class PostgresDatabaseFixture(Fixture):
106
107 def __init__(self):
108 super(PostgresDatabaseFixture, self).__init__()
109 self.db_name = "libdep"
110
111 def drop_db(self):
112 # stub suggests that dropping all tables would be quicker than
113 # dropping the db when the number of tables is small.
114 # select quote_ident(table_schema) || '.' ||
115 # quote_ident(table_name) from information_schema.tables
116 # WHERE table_schema = 'public';
117 self.cluster.dropdb(self.db_name)
118
119 def create_db(self):
120 self.cluster.createdb(self.db_name)
121 queries = [
122 'postgres_schema.sql',
123 'patch-00001.sql',
124 'patch-00002.sql',
125 ]
126 for patch in get_db_schema_queries(queries):
127 self._execute(patch)
128
129 def _execute(self, query):
130 with closing(self.cluster.connect(self.db_name)) as conn:
131 cur = conn.cursor()
132 cur.execute(query)
133 conn.commit()
134
135 def close_connection(self):
136 self.conn.close()
137
138 def open_connection(self):
139 db = create_database(URI(scheme='postgres',
140 host=self.cluster.datadir, database=self.db_name))
141 self.conn = Store(db)
142 self.addCleanup(self.close_connection)
143
144 def reset(self):
145 self.close_connection()
146 self.drop_db()
147 self.create_db()
148 self.open_connection()
149
150 def setUp(self):
151 super(PostgresDatabaseFixture, self).setUp()
152 self.tempdir = self.useFixture(TempDir())
153 self.cluster = self.useFixture(ClusterFixture(self.tempdir.path))
154 self.create_db()
155 self.open_connection()
156
157
158class FixtureResource(_FixtureResource):
159 """The built in FixtureResource doesn't get properly dirtied."""
160 # XXX: workaround for bug 1023423
161
162 def _get_dirty(self):
163 return True
164
165 def _set_dirty(self, new_val):
166 pass
167
168 _dirty = property(_get_dirty, _set_dirty)
169
170
171class PostgresDatabaseResource(FixtureResource):
172
173 def __init__(self):
174 fixture = PostgresDatabaseFixture()
175 super(PostgresDatabaseResource, self).__init__(fixture)
176
177 def reset(self, resource, result=None):
178 resource.reset()
179 return resource
180
181
182postgres_db_resource = PostgresDatabaseResource()
183
184
185class DatabaseConfig(Fixture):
186
187 def __init__(self, db_fixture):
188 super(DatabaseConfig, self).__init__()
189 self.db_fixture = db_fixture
190
191 def setUp(self):
192 super(DatabaseConfig, self).setUp()
193 self.useFixture(
194 ConfigSettings(
195 ('database', {'db_type': 'postgres',
196 'host': self.db_fixture.cluster.datadir,
197 'db_name': self.db_fixture.db_name,
198 })))
199
200
201class DatabaseFixture(Fixture):
202 """Create a temporary database and make it the default.
203
204 Don't use this twice within a test, otherwise you'll get confused.
205 """
206
207 def setUp(self):
208 super(DatabaseFixture, self).setUp()
209 pg_db = self.useFixture(PostgresDatabaseFixture())
210 self.useFixture(DatabaseConfig(pg_db))
211 self.db = PackageDatabase(pg_db.conn)
212 self.addCleanup(self.db.close)
213
214
215def ConfigFileFixture(location):
216 """Use a different configuration file."""
217 return EnvironmentVariableFixture(CONF_FILE_ENV_VAR, location)
218
219
220class ConfigSettings(Fixture):83class ConfigSettings(Fixture):
221 """Use a configuration file with different settings."""84 """Use a configuration file with different settings."""
22285
@@ -239,7 +102,8 @@
239 tempdir = self.useFixture(TempDir())102 tempdir = self.useFixture(TempDir())
240 config_file_path = os.path.join(tempdir.path, 'test.cfg')103 config_file_path = os.path.join(tempdir.path, 'test.cfg')
241 write_config_file(config_file_path, self._settings)104 write_config_file(config_file_path, self._settings)
242 self.useFixture(ConfigFileFixture(config_file_path))105 self.useFixture(
106 EnvironmentVariableFixture(CONF_FILE_ENV_VAR, config_file_path))
243107
244108
245class LibdepFixture(Fixture):109class LibdepFixture(Fixture):
246110
=== removed file 'devportalbinary/tests/test_aptfile.py'
--- devportalbinary/tests/test_aptfile.py 2012-10-26 12:21:03 +0000
+++ devportalbinary/tests/test_aptfile.py 1970-01-01 00:00:00 +0000
@@ -1,53 +0,0 @@
1import gzip
2import os
3
4from mock import patch
5from fixtures import TempDir
6from testtools import TestCase
7
8from ..aptfile import AptFilePackageDatabase
9
10
11class AptFilePackageDatabaseTestCase(TestCase):
12
13 # point to our local contents file version that is a tad smaller
14 CONTENTS_CACHE = os.path.join(
15 os.path.dirname(__file__), "data", "apt-file-backend")
16
17 def setUp(self):
18 super(AptFilePackageDatabaseTestCase, self).setUp()
19 self.db = AptFilePackageDatabase(self.CONTENTS_CACHE)
20
21 def test_read_fixture_contents_worked(self):
22 """ test that our fixture Contents file works as expected """
23 # our test DB has 4 entries in the default search path
24 self.assertEqual(
25 len(self.db._get_lib_to_pkgs_mapping("oneiric", "i386")), 4)
26
27 def test_get_dependencies(self):
28 """ Test that data from the fixture dependencies file works """
29 self.assertEqual(
30 self.db.get_multiple_dependencies(["libz.so.1"], 'i386'),
31 ({'libz.so.1': 'zlib1g'}, set()))
32
33 @patch("urllib.urlretrieve")
34 def test_lazy_downloading(self, mock_urlretrieve):
35 """ test that lazy downloading works """
36 def _put_fixture_contents_file_in_place(url, target):
37 with gzip.open(target, "w") as f:
38 f.write("""
39Some header text that is ignored
40FILE LOCATION
41usr/lib/libfoo.so.2 pkgfoo,pkgbar
42""")
43 tempdir = self.useFixture(TempDir())
44 db = AptFilePackageDatabase(tempdir.path)
45 mock_urlretrieve.side_effect = _put_fixture_contents_file_in_place
46 self.assertEqual(
47 db.get_multiple_dependencies(["libfoo.so.2"], arch="i386"),
48 ({'libfoo.so.2': set(["pkgfoo", "pkgbar"])}, set()))
49 self.assertEqual(len(db._get_lib_to_pkgs_mapping("oneiric", "i386")), 1)
50
51 def test_close(self):
52 # Test that there is a close method we can call
53 self.db.close()
540
=== modified file 'devportalbinary/tests/test_database.py'
--- devportalbinary/tests/test_database.py 2012-11-13 11:22:16 +0000
+++ devportalbinary/tests/test_database.py 2012-11-13 14:53:23 +0000
@@ -1,124 +1,12 @@
1import os
21
3from fixtures import TempDir
4from testresources import ResourcedTestCase
5from testtools import TestCase2from testtools import TestCase
6from testtools.matchers import (
7 Equals,
8 Matcher,
9 )
103
11from devportalbinary.database import (4from devportalbinary.database import LibdepServiceClient
12 AptFilePackageDatabase,5from devportalbinary.testing import get_libdep_service_client
13 get_dependency_database,
14 LibdepServiceClient,
15 load_configuration,
16 PackageDatabase,
17 )
18from devportalbinary.testing import (
19 ConfigFileFixture,
20 ConfigSettings,
21 get_libdep_service_client,
22 postgres_db_resource,
23 )
246
25from libdep_service_client.client import Client7from libdep_service_client.client import Client
268
279
28class ResultsIn(Matcher):
29
30 def __init__(self, db, rows):
31 self._db = db
32 self._rows = rows
33
34 def match(self, query):
35 # XXX: Abstraction violation
36 results = self._db._store.execute(query)
37 return Equals(self._rows).match(list(results))
38
39
40class TestDatabase(TestCase, ResourcedTestCase):
41
42 resources = [
43 ('db_fixture', postgres_db_resource),
44 ]
45
46 def get_package_db(self):
47 db = PackageDatabase(self.db_fixture.conn)
48 self.addCleanup(db.close)
49 return db
50
51 def test_unknown_library(self):
52 db = self.get_package_db()
53 deps = db.get_multiple_dependencies(['libfoo.so.0'], 'i386')
54 self.assertEqual(deps, {})
55
56 def test_close(self):
57 # Test that we can close the package db.
58 db = PackageDatabase(self.db_fixture.conn)
59 db.close()
60
61 def test_close_twice(self):
62 # Test that we can close the package db twice with no exception.
63 db = PackageDatabase(self.db_fixture.conn)
64 db.close()
65 db.close()
66
67
68class TestDatabaseConfiguration(TestCase):
69
70 def use_database_config(self, **db_settings):
71 return self.useFixture(ConfigSettings(('database', db_settings)))
72
73 def test_get_db_info_from_config_sqlite(self):
74 other_tempdir = self.useFixture(TempDir())
75 expected_db_path = os.path.join(other_tempdir.path, 'db')
76 self.use_database_config(db_type='sqlite', path=expected_db_path)
77 options = load_configuration()
78 self.assertRaises(ValueError, PackageDatabase.get_db_info_from_config,
79 options)
80
81 def test_default_create_no_config(self):
82 nonexistent = self.getUniqueString()
83 self.useFixture(ConfigFileFixture(nonexistent))
84 self.assertIsInstance(
85 get_dependency_database(), AptFilePackageDatabase)
86
87 def test_default_create_empty_config(self):
88 self.useFixture(ConfigSettings())
89 self.assertIsInstance(
90 get_dependency_database(), AptFilePackageDatabase)
91
92 def test_remote_service(self):
93 base_url = 'http://example.com/libdep-service/'
94 self.use_database_config(db_type='libdep-service', base_url=base_url)
95 db = get_dependency_database()
96 self.assertIsInstance(db, LibdepServiceClient)
97 self.assertEqual(base_url, db._client.base_url)
98
99 def test_get_db_info_from_config_postgres(self):
100 expected_username = self.getUniqueString()
101 expected_password = self.getUniqueString()
102 expected_host = self.getUniqueString()
103 expected_port = self.getUniqueInteger()
104 expected_db_name = self.getUniqueString()
105
106 self.use_database_config(
107 db_type='postgres',
108 username=expected_username,
109 password=expected_password,
110 host=expected_host,
111 port=expected_port,
112 db_name=expected_db_name)
113 options = load_configuration()
114 uri = PackageDatabase.get_db_info_from_config(options)
115 self.assertEqual(expected_db_name, uri.database)
116 self.assertEqual(expected_port, uri.port)
117 self.assertEqual(expected_host, uri.host)
118 self.assertEqual(expected_password, uri.password)
119 self.assertEqual(expected_username, uri.username)
120
121
122class TestLibdepServiceClient(TestCase):10class TestLibdepServiceClient(TestCase):
12311
124 TEST_DATA = [('libfoo', {'i386': {'libfoo': 'libfoo-bin'}})]12 TEST_DATA = [('libfoo', {'i386': {'libfoo': 'libfoo-bin'}})]
12513
=== modified file 'setup.py'
--- setup.py 2012-11-13 09:14:39 +0000
+++ setup.py 2012-11-13 14:53:23 +0000
@@ -18,6 +18,8 @@
18 )18 )
19from setuptools import setup, find_packages19from setuptools import setup, find_packages
2020
21# XXX: Need to do a trawl to see if we are depending on things that we are no
22# longer using.
2123
22__version__ = get_version('devportalbinary/__init__.py')24__version__ = get_version('devportalbinary/__init__.py')
2325
@@ -36,7 +38,6 @@
36 install_requires = [38 install_requires = [
37 'bzr',39 'bzr',
38 'configglue',40 'configglue',
39 'launchpadlib',
40 'libdep-service-python>=0.0.5',41 'libdep-service-python>=0.0.5',
41 'PIL',42 'PIL',
42 'pkgme>=0.4.1',43 'pkgme>=0.4.1',
@@ -45,7 +46,6 @@
45 ],46 ],
46 entry_points = {47 entry_points = {
47 'console_scripts': [48 'console_scripts': [
48 'dump-apt-file-db=devportalbinary.aptfile:dump_apt_file_db',
49 'guess-executable=devportalbinary.binary:print_executable',49 'guess-executable=devportalbinary.binary:print_executable',
50 'guess-deps=devportalbinary.binary:print_dependencies',50 'guess-deps=devportalbinary.binary:print_dependencies',
51 ],51 ],

Subscribers

People subscribed via source and target branches