Merge lp:~stevenk/launchpad/dsp-vocab into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: Steve Kowalik
Approved revision: no longer in the source branch.
Merged at revision: 13298
Proposed branch: lp:~stevenk/launchpad/dsp-vocab
Merge into: lp:launchpad
Diff against target: 277 lines (+243/-0)
2 files modified
lib/lp/registry/tests/test_dsp_vocabularies.py (+144/-0)
lib/lp/registry/vocabularies.py (+99/-0)
To merge this branch: bzr merge lp:~stevenk/launchpad/dsp-vocab
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Ian Booth (community) Needs Information
Review via email: mp+65762@code.launchpad.net

Commit message

[r=jtv][bug=42298][incr] Add DistributionSourcePackageVocabulary.

Description of the change

Add a new vocab for DistributionSourcePackage. This vocab isn't used by anything yet, but it creates terms, and searches for published sources (and their linked binaries) in the provided distribution. I'm comfortable of the tests I've written, but I'm not certain if I've implemented an IHugeVocabulory correctly.

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

Some initial thoughts:

The toTerm() implementation looks wrong I think. toTerm() takes 3 parameters: obj, token, title. IIRC obj should be the data item represented by the Term and is what your business logic in the form submission callback gets, in this case dsp. I think(?) you want something like:

SimpleTerm(dsp, %s-%s' % (dsp.distribution.name, dsp.name), 'xxxx') where xxxx is whatever user text you want displayed in the ui.

It appears there's scope for the toTerm to execute sql in getting the data it needs. Have you profiled it? Perhaps there's a need to prefetch/eager load the built binaries info.

review: Needs Information
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Don't do column.like('%' + search_term + '%') — that gets messy with the escaping. Use column.contains_string(search_term) instead.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Is there no helper class you can derive your huge vocabulary from? Please have a test self.assertThat(self.vocabulary, Provides(IHugeVocabulary)). That will help clear up (some) API differences from the interface.

Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

I made a few notes about things that should be changed, but apart from those it's fine with me. If this breaks when we start using it, we'll see then. If it's too slow, a timeout on an experimental page will tell us much more than a-priori reasoning. And if it gives the wrong results, we'll call that progressing insight. A branch in devel is worth two in the bush.

One tip: I find unit tests get massively clearer when I name them after the "facts" they establish, rather than just after the function or variation they test. So not just "test_fooBar" but "test_fooBar_accepts_None" and "test_fooBar_returns_fooed_bar" and so on.

This comes from the behaviorologist or behavior-oriented or best-behavior or something along those lines school of software development that I've heard faint whisperings of.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'lib/lp/registry/tests/test_dsp_vocabularies.py'
2--- lib/lp/registry/tests/test_dsp_vocabularies.py 1970-01-01 00:00:00 +0000
3+++ lib/lp/registry/tests/test_dsp_vocabularies.py 2011-06-24 09:31:38 +0000
4@@ -0,0 +1,144 @@
5+# Copyright 2011 Canonical Ltd. This software is licensed under the
6+# GNU Affero General Public License version 3 (see the file LICENSE).
7+
8+"""Test the Distribution Source Package vocabulary."""
9+
10+__metaclass__ = type
11+
12+from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
13+from canonical.testing.layers import DatabaseFunctionalLayer
14+from lp.registry.vocabularies import DistributionSourcePackageVocabulary
15+from lp.testing import TestCaseWithFactory
16+
17+
18+class TestDistributionSourcePackageVocabulary(TestCaseWithFactory):
19+ """Test that the DistributionSourcePackageVocabulary behaves as
20+ expected."""
21+ layer = DatabaseFunctionalLayer
22+
23+ def setUp(self):
24+ super(TestDistributionSourcePackageVocabulary, self).setUp()
25+ self.vocabulary = DistributionSourcePackageVocabulary()
26+
27+ def test_provides_ihugevocabulary(self):
28+ self.assertProvides(self.vocabulary, IHugeVocabulary)
29+
30+ def test_toTerm_unbuilt_dsp(self):
31+ # If the source has no built binaries, the term's value contains a
32+ # string to that effect.
33+ dsp = self.factory.makeDistributionSourcePackage(
34+ sourcepackagename='foo')
35+ term = self.vocabulary.toTerm(dsp)
36+ self.assertEqual(dsp.sourcepackagename.name, term.title)
37+ expected_token = '%s-%s' % (dsp.distribution.name, dsp.name)
38+ self.assertEqual(expected_token, term.token)
39+ self.assertEqual('Not yet built.', term.value)
40+
41+ def test_toTerm_built_single_binary(self):
42+ # The binary package name appears in the term's value.
43+ bpph = self.factory.makeBinaryPackagePublishingHistory()
44+ spr = bpph.binarypackagerelease.build.source_package_release
45+ dsp = self.factory.makeDistributionSourcePackage(
46+ sourcepackagename=spr.sourcepackagename,
47+ distribution=bpph.distroseries.distribution)
48+ term = self.vocabulary.toTerm(dsp)
49+ expected_token = '%s-%s' % (dsp.distribution.name, dsp.name)
50+ self.assertEqual(expected_token, term.token)
51+ self.assertEqual(bpph.binary_package_name, term.value)
52+
53+ def test_toTerm_built_multiple_binary(self):
54+ # All of the binary package names appear in the term's value.
55+ spph = self.factory.makeSourcePackagePublishingHistory()
56+ spr = spph.sourcepackagerelease
57+ das = self.factory.makeDistroArchSeries(
58+ distroseries=spph.distroseries)
59+ expected_names = []
60+ for i in xrange(20):
61+ bpb = self.factory.makeBinaryPackageBuild(
62+ source_package_release=spr, distroarchseries=das)
63+ bpr = self.factory.makeBinaryPackageRelease(build=bpb)
64+ expected_names.append(bpr.name)
65+ self.factory.makeBinaryPackagePublishingHistory(
66+ binarypackagerelease=bpr, distroarchseries=das)
67+ dsp = spr.distrosourcepackage
68+ term = self.vocabulary.toTerm(dsp)
69+ expected_token = '%s-%s' % (dsp.distribution.name, dsp.name)
70+ self.assertEqual(expected_token, term.token)
71+ self.assertEqual(', '.join(expected_names), term.value)
72+
73+ def test_searchForTerms_None(self):
74+ # Searching for nothing gets you that.
75+ results = self.vocabulary.searchForTerms()
76+ self.assertIs(None, results)
77+
78+ def assertTermsEqual(self, expected, actual):
79+ # Assert two given terms are equal.
80+ self.assertEqual(expected.token, actual.token)
81+ self.assertEqual(expected.title, actual.title)
82+ self.assertEqual(expected.value, actual.value)
83+
84+ def test_searchForTerms_published_source(self):
85+ # When we search for a source package name that is published, a DSP
86+ # is returned.
87+ spph = self.factory.makeSourcePackagePublishingHistory()
88+ vocabulary = DistributionSourcePackageVocabulary(
89+ context=spph.distroseries.distribution)
90+ results = vocabulary.searchForTerms(query=spph.source_package_name)
91+ dsp = self.factory.makeDistributionSourcePackage(
92+ sourcepackagename=spph.source_package_name,
93+ distribution=spph.distroseries.distribution)
94+ self.assertTermsEqual(vocabulary.toTerm(dsp), results[0])
95+
96+ def test_searchForTerms_unpublished_source(self):
97+ # If the source package name isn't published in the distribution,
98+ # we get no results.
99+ spph = self.factory.makeSourcePackagePublishingHistory()
100+ vocabulary = DistributionSourcePackageVocabulary(
101+ context=self.factory.makeDistribution())
102+ results = vocabulary.searchForTerms(query=spph.source_package_name)
103+ self.assertEqual([], list(results))
104+
105+ def test_searchForTerms_unpublished_binary(self):
106+ # If the binary package name isn't published in the distribution,
107+ # we get no results.
108+ bpph = self.factory.makeBinaryPackagePublishingHistory()
109+ vocabulary = DistributionSourcePackageVocabulary(
110+ context=self.factory.makeDistribution())
111+ results = vocabulary.searchForTerms(query=bpph.binary_package_name)
112+ self.assertEqual([], list(results))
113+
114+ def test_searchForTerms_published_binary(self):
115+ # We can search for a binary package name, which returns the DSP.
116+ bpph = self.factory.makeBinaryPackagePublishingHistory()
117+ distribution = bpph.distroarchseries.distroseries.distribution
118+ vocabulary = DistributionSourcePackageVocabulary(
119+ context=distribution)
120+ spn = bpph.binarypackagerelease.build.source_package_release.name
121+ dsp = self.factory.makeDistributionSourcePackage(
122+ sourcepackagename=spn, distribution=distribution)
123+ results = vocabulary.searchForTerms(query=bpph.binary_package_name)
124+ self.assertTermsEqual(vocabulary.toTerm(dsp), results[0])
125+
126+ def test_searchForTerms_published_multiple_binaries(self):
127+ # Searching for a subset of a binary package name returns the DSP
128+ # that built the binary package.
129+ spn = self.factory.getOrMakeSourcePackageName('xorg')
130+ spr = self.factory.makeSourcePackageRelease(sourcepackagename=spn)
131+ das = self.factory.makeDistroArchSeries()
132+ spph = self.factory.makeSourcePackagePublishingHistory(
133+ sourcepackagerelease=spr, distroseries=das.distroseries)
134+ for name in ('xorg-common', 'xorg-server', 'xorg-video-intel'):
135+ bpn = self.factory.getOrMakeBinaryPackageName(name)
136+ bpb = self.factory.makeBinaryPackageBuild(
137+ source_package_release=spr, distroarchseries=das)
138+ bpr = self.factory.makeBinaryPackageRelease(
139+ binarypackagename=bpn, build=bpb)
140+ bpph = self.factory.makeBinaryPackagePublishingHistory(
141+ binarypackagerelease=bpr, distroarchseries=das)
142+ dsp = self.factory.makeDistributionSourcePackage(
143+ distribution=das.distroseries.distribution,
144+ sourcepackagename=spn)
145+ vocabulary = DistributionSourcePackageVocabulary(
146+ context=das.distroseries.distribution)
147+ results = vocabulary.searchForTerms(query='xorg-se')
148+ self.assertTermsEqual(vocabulary.toTerm(dsp), results[0])
149
150=== modified file 'lib/lp/registry/vocabularies.py'
151--- lib/lp/registry/vocabularies.py 2011-06-21 06:56:29 +0000
152+++ lib/lp/registry/vocabularies.py 2011-06-24 09:31:38 +0000
153@@ -30,6 +30,7 @@
154 'CommercialProjectsVocabulary',
155 'DistributionOrProductOrProjectGroupVocabulary',
156 'DistributionOrProductVocabulary',
157+ 'DistributionSourcePackageVocabulary',
158 'DistributionVocabulary',
159 'DistroSeriesDerivationVocabulary',
160 'DistroSeriesVocabulary',
161@@ -167,6 +168,9 @@
162 from lp.registry.interfaces.projectgroup import IProjectGroup
163 from lp.registry.interfaces.sourcepackage import ISourcePackage
164 from lp.registry.model.distribution import Distribution
165+from lp.registry.model.distributionsourcepackage import (
166+ DistributionSourcePackage,
167+ )
168 from lp.registry.model.distroseries import DistroSeries
169 from lp.registry.model.distroseriesparent import DistroSeriesParent
170 from lp.registry.model.featuredproject import FeaturedProject
171@@ -190,7 +194,16 @@
172 cachedproperty,
173 get_property_cache,
174 )
175+from lp.soyuz.enums import PackagePublishingStatus
176+from lp.soyuz.model.binarypackagebuild import BinaryPackageBuild
177+from lp.soyuz.model.binarypackagename import BinaryPackageName
178+from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
179 from lp.soyuz.model.distroarchseries import DistroArchSeries
180+from lp.soyuz.model.publishing import (
181+ BinaryPackagePublishingHistory,
182+ SourcePackagePublishingHistory,
183+ )
184+from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
185
186
187 class BasePersonVocabulary:
188@@ -1949,3 +1962,89 @@
189 # package names are always lowercase.
190 return super(SourcePackageNameVocabulary, self).getTermByToken(
191 token.lower())
192+
193+
194+class DistributionSourcePackageVocabulary:
195+
196+ implements(IHugeVocabulary)
197+ displayname = 'Select a package'
198+ step_title = 'Search'
199+
200+ def __init__(self, context=None):
201+ self.context = context
202+
203+ def __contains__(self, obj):
204+ pass
205+
206+ def __iter__(self):
207+ pass
208+
209+ def __len__(self):
210+ pass
211+
212+ def toTerm(self, dsp):
213+ """See `IVocabulary`."""
214+ # SimpleTerm(value, token=None, title=None)
215+ if dsp.publishing_history:
216+ binaries = dsp.publishing_history[0].getBuiltBinaries()
217+ summary = ', '.join(
218+ [binary.binary_package_name for binary in binaries])
219+ else:
220+ summary = "Not yet built."
221+ token = '%s-%s' % (dsp.distribution.name, dsp.name)
222+ return SimpleTerm(summary, token, dsp.name)
223+
224+ def getTerm(self, dsp):
225+ """See `IBaseVocabulary`."""
226+ return self.toTerm(dsp)
227+
228+ def getTermByToken(self, token):
229+ """See `IVocabularyTokenized`."""
230+ pass
231+
232+ def searchForTerms(self, query=None):
233+ """See `IHugeVocabulary`."""
234+ distribution = self.context
235+ if query is None:
236+ return
237+ search_term = unicode(query)
238+ store = IStore(SourcePackagePublishingHistory)
239+ spns = store.using(
240+ SourcePackagePublishingHistory,
241+ LeftJoin(
242+ SourcePackageRelease,
243+ SourcePackagePublishingHistory.sourcepackagereleaseID ==
244+ SourcePackageRelease.id),
245+ LeftJoin(
246+ SourcePackageName,
247+ SourcePackageRelease.sourcepackagenameID ==
248+ SourcePackageName.id),
249+ LeftJoin(
250+ DistroSeries,
251+ SourcePackagePublishingHistory.distroseriesID ==
252+ DistroSeries.id),
253+ LeftJoin(
254+ BinaryPackageBuild,
255+ BinaryPackageBuild.source_package_release_id ==
256+ SourcePackageRelease.id),
257+ LeftJoin(
258+ BinaryPackageRelease,
259+ BinaryPackageRelease.buildID == BinaryPackageBuild.id),
260+ LeftJoin(
261+ BinaryPackageName,
262+ BinaryPackageRelease.binarypackagenameID ==
263+ BinaryPackageName.id
264+ )).find(
265+ SourcePackageName,
266+ DistroSeries.distributionID == distribution.id,
267+ SourcePackagePublishingHistory.status.is_in((
268+ PackagePublishingStatus.PENDING,
269+ PackagePublishingStatus.PUBLISHED)),
270+ SourcePackagePublishingHistory.archive ==
271+ distribution.main_archive,
272+ Or(
273+ SourcePackageName.name.contains_string(search_term),
274+ BinaryPackageName.name.contains_string(
275+ search_term))).config(distinct=True)
276+ return [
277+ self.toTerm(distribution.getSourcePackage(spn)) for spn in spns]