Merge lp:~rockstar/launchpad/unsupported-series-recipe-api into lp:launchpad

Proposed by Paul Hummer
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11187
Proposed branch: lp:~rockstar/launchpad/unsupported-series-recipe-api
Merge into: lp:launchpad
Prerequisite: lp:~rockstar/launchpad/create-recipe-error-messages
Diff against target: 518 lines (+120/-112)
10 files modified
database/schema/security.cfg (+2/-0)
lib/lp/code/browser/configure.zcml (+2/-2)
lib/lp/code/browser/sourcepackagerecipe.py (+4/-36)
lib/lp/code/errors.py (+12/-0)
lib/lp/code/model/sourcepackagerecipe.py (+22/-2)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+22/-21)
lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved (+0/-40)
lib/lp/code/model/tests/test_sourcepackagerecipebuild.py (+6/-6)
lib/lp/code/vocabularies/sourcepackagerecipe.py (+38/-0)
lib/lp/testing/factory.py (+12/-5)
To merge this branch: bzr merge lp:~rockstar/launchpad/unsupported-series-recipe-api
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+30397@code.launchpad.net

Description of the change

This branch fixes bug #607125 - Yesterday, we had a user request builds for distroserieses that we don't have chroots for anymore using the API. At least, we SUSPECT that it was through the API, since the UI prevents this.

Going on the theory that it was through the API and that it's not possible to do through the UI, I took the code that helps the UI show the correct set of distroserieses and moved it to lp.code.model.sourcepackagerecipe. I also moved the vocabularies into the (already existing) lp.code.vocabularies.sourcepackagerecipe.

After everything was moved around, I made it so that ISourcePackageRecipe.requestBuild checked the distroseries to make sure it was valid. I ran the tests there, and found a few breakages. While fixing these breakages, I found the unfortunate case where I needed to use sample data on Ubuntu Hardy and Warty because makeArchive automatically makes the Archive use Ubuntu. After talking to jml about it, I decided to make a specific function for this need so that when we entirely get rid of sampledata, it's a little plainer what's going on.

Once the tests were passing again, I wrote some a webservice test to stop that vector.

If you see random oddness in my branch, it's because I took the liberty of cleaning up some lint I found in 'make lint.' Make lint is also a little brain-dead in thinking that you can't have a single item tuple have a comma without whitespace. I didn't fix those, because I thought that rule was stupid... I'm happy to fix it if you're that hung up on it.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

get_buildable_distroseries_set could use a docstring - just a little one noting why its not doing the search at the sql layer perhaps?

+ new_distroseries = archive.distribution.getSeries('hoary')

doesn't seem to use your helper function - could it?

copy=n=paste mismatch:

294 + def test_requestBuildRejectUnsupportedDistroSeries(self):
295 + """Build requests are rejected if they exceed quota."""

this :

369 + second_distroseries = \
370 + self.factory.makeSourcePackageRecipeDistroseries("hoary")

is that a change to use another non-sample-data-tied helper?

Generally fine to land, consider these recommendations.

Thanks,
Rob

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2010-06-22 16:08:05 +0000
+++ database/schema/security.cfg 2010-07-21 09:22:01 +0000
@@ -694,9 +694,11 @@
694type=user694type=user
695groups=script695groups=script
696public.archive = SELECT696public.archive = SELECT
697public.archivepermission = SELECT
697public.buildqueue = SELECT, INSERT, UPDATE698public.buildqueue = SELECT, INSERT, UPDATE
698public.branch = SELECT699public.branch = SELECT
699public.component = SELECT700public.component = SELECT
701public.distribution = SELECT
700public.distroseries = SELECT702public.distroseries = SELECT
701public.distroarchseries = SELECT703public.distroarchseries = SELECT
702public.job = SELECT, INSERT704public.job = SELECT, INSERT
703705
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2010-07-14 10:09:35 +0000
+++ lib/lp/code/browser/configure.zcml 2010-07-21 09:22:01 +0000
@@ -1201,14 +1201,14 @@
1201 </facet>1201 </facet>
1202 <securedutility1202 <securedutility
1203 name="BuildableDistroSeries"1203 name="BuildableDistroSeries"
1204 component="lp.code.browser.sourcepackagerecipe.buildable_distroseries_vocabulary"1204 component="lp.code.vocabularies.sourcepackagerecipe.buildable_distroseries_vocabulary"
1205 provides="zope.schema.interfaces.IVocabularyFactory"1205 provides="zope.schema.interfaces.IVocabularyFactory"
1206 >1206 >
1207 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>1207 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
1208 </securedutility>1208 </securedutility>
1209 <securedutility1209 <securedutility
1210 name="TargetPPAs"1210 name="TargetPPAs"
1211 component="lp.code.browser.sourcepackagerecipe.target_ppas_vocabulary"1211 component="lp.code.vocabularies.sourcepackagerecipe.target_ppas_vocabulary"
1212 provides="zope.schema.interfaces.IVocabularyFactory"1212 provides="zope.schema.interfaces.IVocabularyFactory"
1213 >1213 >
1214 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>1214 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
12151215
=== modified file 'lib/lp/code/browser/sourcepackagerecipe.py'
--- lib/lp/code/browser/sourcepackagerecipe.py 2010-07-19 10:31:16 +0000
+++ lib/lp/code/browser/sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
@@ -24,18 +24,14 @@
24from zope.event import notify24from zope.event import notify
25from zope.interface import implements, Interface25from zope.interface import implements, Interface
26from zope.schema import Choice, List, Text26from zope.schema import Choice, List, Text
27from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
2827
29from canonical.database.constants import UTC_NOW28from canonical.database.constants import UTC_NOW
30from canonical.launchpad.browser.launchpad import Hierarchy29from canonical.launchpad.browser.launchpad import Hierarchy
31from canonical.launchpad.interfaces import ILaunchBag
32from canonical.launchpad.webapp import (30from canonical.launchpad.webapp import (
33 action, canonical_url, ContextMenu, custom_widget,31 action, canonical_url, ContextMenu, custom_widget,
34 enabled_with_permission, LaunchpadEditFormView, LaunchpadFormView,32 enabled_with_permission, LaunchpadEditFormView, LaunchpadFormView,
35 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough, structured)33 LaunchpadView, Link, Navigation, NavigationMenu, stepthrough, structured)
36from canonical.launchpad.webapp.authorization import check_permission
37from canonical.launchpad.webapp.breadcrumb import Breadcrumb34from canonical.launchpad.webapp.breadcrumb import Breadcrumb
38from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
39from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget35from canonical.widgets.itemswidgets import LabeledMultiCheckBoxWidget
40from lp.code.errors import ForbiddenInstruction36from lp.code.errors import ForbiddenInstruction
41from lp.code.errors import BuildAlreadyPending37from lp.code.errors import BuildAlreadyPending
@@ -44,10 +40,6 @@
44 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)40 ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT)
45from lp.code.interfaces.sourcepackagerecipebuild import (41from lp.code.interfaces.sourcepackagerecipebuild import (
46 ISourcePackageRecipeBuildSource)42 ISourcePackageRecipeBuildSource)
47from lp.soyuz.browser.archive import make_archive_vocabulary
48from lp.soyuz.interfaces.archive import (
49 IArchiveSet)
50from lp.registry.interfaces.distroseries import IDistroSeriesSet
51from lp.registry.interfaces.pocket import PackagePublishingPocket43from lp.registry.interfaces.pocket import PackagePublishingPocket
5244
53RECIPE_BETA_MESSAGE = structured(45RECIPE_BETA_MESSAGE = structured(
@@ -148,8 +140,8 @@
148 """Default view of a SourcePackageRecipe."""140 """Default view of a SourcePackageRecipe."""
149141
150 def initialize(self):142 def initialize(self):
151 # XXX: rockstar: This should be removed when source package recipes are143 # XXX: rockstar: This should be removed when source package recipes
152 # put into production. spec=sourcepackagerecipes144 # are put into production. spec=sourcepackagerecipes
153 super(SourcePackageRecipeView, self).initialize()145 super(SourcePackageRecipeView, self).initialize()
154 self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)146 self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
155147
@@ -177,28 +169,6 @@
177 return builds169 return builds
178170
179171
180def buildable_distroseries_vocabulary(context):
181 """Return a vocabulary of buildable distroseries."""
182 ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
183 supported_distros = [ppa.distribution for ppa in ppas]
184 dsset = getUtility(IDistroSeriesSet).search()
185 terms = sorted_dotted_numbers(
186 [SimpleTerm(distro, distro.id, distro.displayname)
187 for distro in dsset if (
188 distro.active and distro.distribution in supported_distros)],
189 key=lambda term: term.value.version)
190 terms.reverse()
191 return SimpleVocabulary(terms)
192
193
194def target_ppas_vocabulary(context):
195 """Return a vocabulary of ppas that the current user can target."""
196 ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
197 return make_archive_vocabulary(
198 ppa for ppa in ppas
199 if check_permission('launchpad.Append', ppa))
200
201
202class SourcePackageRecipeRequestBuildsView(LaunchpadFormView):172class SourcePackageRecipeRequestBuildsView(LaunchpadFormView):
203 """A view for requesting builds of a SourcePackageRecipe."""173 """A view for requesting builds of a SourcePackageRecipe."""
204174
@@ -261,7 +231,6 @@
261 self.next_url = self.cancel_url231 self.next_url = self.cancel_url
262232
263233
264
265class ISourcePackageAddEditSchema(Interface):234class ISourcePackageAddEditSchema(Interface):
266 """Schema for adding or editing a recipe."""235 """Schema for adding or editing a recipe."""
267236
@@ -281,7 +250,6 @@
281 description=u'The text of the recipe.')250 description=u'The text of the recipe.')
282251
283252
284
285class RecipeTextValidatorMixin:253class RecipeTextValidatorMixin:
286 """Class to validate that the Source Package Recipe text is valid."""254 """Class to validate that the Source Package Recipe text is valid."""
287255
@@ -310,8 +278,8 @@
310 custom_widget('distros', LabeledMultiCheckBoxWidget)278 custom_widget('distros', LabeledMultiCheckBoxWidget)
311279
312 def initialize(self):280 def initialize(self):
313 # XXX: rockstar: This should be removed when source package recipes are281 # XXX: rockstar: This should be removed when source package recipes
314 # put into production. spec=sourcepackagerecipes282 # are put into production. spec=sourcepackagerecipes
315 super(SourcePackageRecipeAddView, self).initialize()283 super(SourcePackageRecipeAddView, self).initialize()
316 self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)284 self.request.response.addWarningNotification(RECIPE_BETA_MESSAGE)
317285
318286
=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py 2010-06-17 20:45:58 +0000
+++ lib/lp/code/errors.py 2010-07-21 09:22:01 +0000
@@ -8,6 +8,7 @@
8 'BadBranchMergeProposalSearchContext',8 'BadBranchMergeProposalSearchContext',
9 'BadStateTransition',9 'BadStateTransition',
10 'BuildAlreadyPending',10 'BuildAlreadyPending',
11 'BuildNotAllowedForDistro',
11 'BranchMergeProposalExists',12 'BranchMergeProposalExists',
12 'CodeImportAlreadyRequested',13 'CodeImportAlreadyRequested',
13 'CodeImportAlreadyRunning',14 'CodeImportAlreadyRunning',
@@ -144,3 +145,14 @@
144 RecipeBuildException.__init__(145 RecipeBuildException.__init__(
145 self, recipe, distroseries,146 self, recipe, distroseries,
146 'An identical build of this recipe is already pending.')147 'An identical build of this recipe is already pending.')
148
149
150class BuildNotAllowedForDistro(RecipeBuildException):
151 """A build was requested against an unsupported distroseries."""
152
153 webservice_error(400)
154
155 def __init__(self, recipe, distroseries):
156 RecipeBuildException.__init__(
157 self, recipe, distroseries,
158 'A build against this distro is not allowed.')
147159
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2010-07-19 15:32:21 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
@@ -7,6 +7,7 @@
77
8__metaclass__ = type8__metaclass__ = type
9__all__ = [9__all__ = [
10 'get_buildable_distroseries_set',
10 'SourcePackageRecipe',11 'SourcePackageRecipe',
11 ]12 ]
1213
@@ -25,7 +26,8 @@
25from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore26from canonical.launchpad.interfaces.lpstorm import IMasterStore, IStore
2627
27from lp.buildmaster.interfaces.buildbase import BuildStatus28from lp.buildmaster.interfaces.buildbase import BuildStatus
28from lp.code.errors import BuildAlreadyPending, TooManyBuilds29from lp.code.errors import (BuildAlreadyPending, BuildNotAllowedForDistro,
30 TooManyBuilds)
29from lp.code.interfaces.sourcepackagerecipe import (31from lp.code.interfaces.sourcepackagerecipe import (
30 ISourcePackageRecipe, ISourcePackageRecipeSource,32 ISourcePackageRecipe, ISourcePackageRecipeSource,
31 ISourcePackageRecipeData)33 ISourcePackageRecipeData)
@@ -33,11 +35,24 @@
33 ISourcePackageRecipeBuildSource)35 ISourcePackageRecipeBuildSource)
34from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild36from lp.code.model.sourcepackagerecipebuild import SourcePackageRecipeBuild
35from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData37from lp.code.model.sourcepackagerecipedata import SourcePackageRecipeData
38from lp.registry.interfaces.distroseries import IDistroSeriesSet
36from lp.registry.model.distroseries import DistroSeries39from lp.registry.model.distroseries import DistroSeries
37from lp.soyuz.interfaces.archive import ArchivePurpose40from lp.soyuz.interfaces.archive import ArchivePurpose, IArchiveSet
38from lp.soyuz.interfaces.component import IComponentSet41from lp.soyuz.interfaces.component import IComponentSet
3942
4043
44def get_buildable_distroseries_set(user):
45 ppas = getUtility(IArchiveSet).getPPAsForUser(user)
46 supported_distros = [ppa.distribution for ppa in ppas]
47 distros = getUtility(IDistroSeriesSet).search()
48
49 buildables = []
50 for distro in distros:
51 if distro.active and distro.distribution in supported_distros:
52 buildables.append(distro)
53 return buildables
54
55
41class NonPPABuildRequest(Exception):56class NonPPABuildRequest(Exception):
42 """A build was requested to a non-PPA and this is currently57 """A build was requested to a non-PPA and this is currently
43 unsupported."""58 unsupported."""
@@ -188,6 +203,11 @@
188 if archive.purpose != ArchivePurpose.PPA:203 if archive.purpose != ArchivePurpose.PPA:
189 raise NonPPABuildRequest204 raise NonPPABuildRequest
190 component = getUtility(IComponentSet)["multiverse"]205 component = getUtility(IComponentSet)["multiverse"]
206
207 buildable_distros = get_buildable_distroseries_set(archive.owner)
208 if distroseries not in buildable_distros:
209 raise BuildNotAllowedForDistro(self, distroseries)
210
191 reject_reason = archive.checkUpload(211 reject_reason = archive.checkUpload(
192 requester, self.distroseries, None, component, pocket)212 requester, self.distroseries, None, component, pocket)
193 if reject_reason is not None:213 if reject_reason is not None:
194214
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-06-18 19:36:55 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
@@ -45,7 +45,6 @@
45from lp.registry.interfaces.pocket import PackagePublishingPocket45from lp.registry.interfaces.pocket import PackagePublishingPocket
46from lp.services.job.interfaces.job import (46from lp.services.job.interfaces.job import (
47 IJob, JobStatus)47 IJob, JobStatus)
48from lp.soyuz.model.processor import ProcessorFamily
49from lp.testing import (48from lp.testing import (
50 ANONYMOUS, launchpadlib_for, login, login_person, person_logged_in,49 ANONYMOUS, launchpadlib_for, login, login_person, person_logged_in,
51 TestCaseWithFactory, ws_object)50 TestCaseWithFactory, ws_object)
@@ -167,7 +166,6 @@
167 branch2 = self.factory.makeAnyBranch()166 branch2 = self.factory.makeAnyBranch()
168 builder_recipe2 = self.factory.makeRecipe(branch2)167 builder_recipe2 = self.factory.makeRecipe(branch2)
169 login_person(sp_recipe.owner.teamowner)168 login_person(sp_recipe.owner.teamowner)
170 #import pdb; pdb.set_trace()
171 sp_recipe.builder_recipe = builder_recipe2169 sp_recipe.builder_recipe = builder_recipe2
172 self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))170 self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
173171
@@ -297,6 +295,7 @@
297 name=u'myrecipe', owner=requester)295 name=u'myrecipe', owner=requester)
298 series = list(recipe.distroseries)[0]296 series = list(recipe.distroseries)[0]
299 archive = self.factory.makeArchive(owner=requester)297 archive = self.factory.makeArchive(owner=requester)
298
300 def request_build():299 def request_build():
301 build = recipe.requestBuild(archive, requester, series,300 build = recipe.requestBuild(archive, requester, series,
302 PackagePublishingPocket.RELEASE)301 PackagePublishingPocket.RELEASE)
@@ -322,10 +321,8 @@
322 self.factory.makeArchive(owner=recipe.owner), recipe.owner,321 self.factory.makeArchive(owner=recipe.owner), recipe.owner,
323 series, PackagePublishingPocket.RELEASE)322 series, PackagePublishingPocket.RELEASE)
324 # Varying distroseries allows build.323 # Varying distroseries allows build.
325 new_distroseries = self.factory.makeDistroSeries()324 new_distroseries = self.factory.makeSourcePackageRecipeDistroseries(
326 new_distroseries.nominatedarchindep = new_distroseries.newArch(325 "hoary")
327 'i386', ProcessorFamily.get(1), False, recipe.owner,
328 supports_virtualized=True)
329 recipe.requestBuild(archive, recipe.owner,326 recipe.requestBuild(archive, recipe.owner,
330 new_distroseries, PackagePublishingPocket.RELEASE)327 new_distroseries, PackagePublishingPocket.RELEASE)
331 # Changing status of old build allows new build.328 # Changing status of old build allows new build.
@@ -430,6 +427,7 @@
430 build.buildduration = timedelta(minutes=10)427 build.buildduration = timedelta(minutes=10)
431 self.assertEqual(428 self.assertEqual(
432 timedelta(minutes=10), recipe.getMedianBuildDuration())429 timedelta(minutes=10), recipe.getMedianBuildDuration())
430
433 def addBuild(minutes):431 def addBuild(minutes):
434 build = removeSecurityProxy(432 build = removeSecurityProxy(
435 self.factory.makeSourcePackageRecipeBuild(recipe=recipe))433 self.factory.makeSourcePackageRecipeBuild(recipe=recipe))
@@ -691,11 +689,7 @@
691 """Build requests can be performed."""689 """Build requests can be performed."""
692 person = self.factory.makePerson()690 person = self.factory.makePerson()
693 archive = self.factory.makeArchive(owner=person)691 archive = self.factory.makeArchive(owner=person)
694 distroseries = self.factory.makeDistroSeries()692 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
695 distroseries_i386 = distroseries.newArch(
696 'i386', ProcessorFamily.get(1), False, person,
697 supports_virtualized=True)
698 distroseries.nominatedarchindep = distroseries_i386
699693
700 recipe, user, launchpad = self.makeRecipe(person)694 recipe, user, launchpad = self.makeRecipe(person)
701 distroseries = ws_object(launchpad, distroseries)695 distroseries = ws_object(launchpad, distroseries)
@@ -708,11 +702,7 @@
708 """Build requests are rejected if already pending."""702 """Build requests are rejected if already pending."""
709 person = self.factory.makePerson()703 person = self.factory.makePerson()
710 archive = self.factory.makeArchive(owner=person)704 archive = self.factory.makeArchive(owner=person)
711 distroseries = self.factory.makeDistroSeries()705 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
712 distroseries_i386 = distroseries.newArch(
713 'i386', ProcessorFamily.get(1), False, person,
714 supports_virtualized=True)
715 distroseries.nominatedarchindep = distroseries_i386
716706
717 recipe, user, launchpad = self.makeRecipe(person)707 recipe, user, launchpad = self.makeRecipe(person)
718 distroseries = ws_object(launchpad, distroseries)708 distroseries = ws_object(launchpad, distroseries)
@@ -729,11 +719,7 @@
729 """Build requests are rejected if they exceed quota."""719 """Build requests are rejected if they exceed quota."""
730 person = self.factory.makePerson()720 person = self.factory.makePerson()
731 archives = [self.factory.makeArchive(owner=person) for x in range(6)]721 archives = [self.factory.makeArchive(owner=person) for x in range(6)]
732 distroseries = self.factory.makeDistroSeries()722 distroseries = self.factory.makeSourcePackageRecipeDistroseries()
733 distroseries_i386 = distroseries.newArch(
734 'i386', ProcessorFamily.get(1), False, person,
735 supports_virtualized=True)
736 distroseries.nominatedarchindep = distroseries_i386
737723
738 recipe, user, launchpad = self.makeRecipe(person)724 recipe, user, launchpad = self.makeRecipe(person)
739 distroseries = ws_object(launchpad, distroseries)725 distroseries = ws_object(launchpad, distroseries)
@@ -749,6 +735,21 @@
749 pocket=PackagePublishingPocket.RELEASE.title)735 pocket=PackagePublishingPocket.RELEASE.title)
750 self.assertIn('TooManyBuilds', str(e))736 self.assertIn('TooManyBuilds', str(e))
751737
738 def test_requestBuildRejectUnsupportedDistroSeries(self):
739 """Build requests are rejected if they have a bad distroseries."""
740 person = self.factory.makePerson()
741 archives = [self.factory.makeArchive(owner=person) for x in range(6)]
742 distroseries = self.factory.makeDistroSeries()
743
744 recipe, user, launchpad = self.makeRecipe(person)
745 distroseries = ws_object(launchpad, distroseries)
746 archive = ws_object(launchpad, archives[-1])
747
748 e = self.assertRaises(Exception, recipe.requestBuild,
749 archive=archive, distroseries=distroseries,
750 pocket=PackagePublishingPocket.RELEASE.title)
751 self.assertIn('BuildNotAllowedForDistro', str(e))
752
752753
753def test_suite():754def test_suite():
754 return unittest.TestLoader().loadTestsFromName(__name__)755 return unittest.TestLoader().loadTestsFromName(__name__)
755756
=== removed file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved 2010-03-26 19:29:56 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py.moved 1970-01-01 00:00:00 +0000
@@ -1,40 +0,0 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
5__metaclass__ = type
6
7
8import unittest
9
10from canonical.testing import DatabaseFunctionalLayer
11from lp.testing import login_person, TestCaseWithFactory
12
13
14class TestSourcePackageRecipe(TestCaseWithFactory):
15
16 layer = DatabaseFunctionalLayer
17
18 def test_distroseries(self):
19 """Test that the distroseries behaves as a set."""
20 recipe = self.factory.makeSourcePackageRecipe()
21 distroseries = self.factory.makeDistroSeries()
22 (old_distroseries,) = recipe.distroseries
23 recipe.distroseries.add(distroseries)
24 self.assertEqual(
25 set([distroseries, old_distroseries]), set(recipe.distroseries))
26 recipe.distroseries.remove(distroseries)
27 self.assertEqual([old_distroseries], list(recipe.distroseries))
28 recipe.distroseries.clear()
29 self.assertEqual([], list(recipe.distroseries))
30
31 def test_build_daily(self):
32 """Test that build_daily behaves as a bool."""
33 recipe = self.factory.makeSourcePackageRecipe()
34 self.assertFalse(recipe.build_daily)
35 login_person(recipe.owner)
36 recipe.build_daily = True
37 self.assertTrue(recipe.build_daily)
38
39def test_suite():
40 return unittest.TestLoader().loadTestsFromName(__name__)
410
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-14 08:42:01 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipebuild.py 2010-07-21 09:22:01 +0000
@@ -249,10 +249,8 @@
249 recipe.requestBuild(249 recipe.requestBuild(
250 recipe.daily_build_archive, recipe.owner, first_distroseries,250 recipe.daily_build_archive, recipe.owner, first_distroseries,
251 PackagePublishingPocket.RELEASE)251 PackagePublishingPocket.RELEASE)
252 second_distroseries = self.factory.makeDistroSeries()252 second_distroseries = \
253 second_distroseries.nominatedarchindep = second_distroseries.newArch(253 self.factory.makeSourcePackageRecipeDistroseries("hoary")
254 'i386', ProcessorFamily.get(1), False, self.factory.makePerson(),
255 supports_virtualized=True)
256 recipe.distroseries.add(second_distroseries)254 recipe.distroseries.add(second_distroseries)
257 builds = SourcePackageRecipeBuild.makeDailyBuilds()255 builds = SourcePackageRecipeBuild.makeDailyBuilds()
258 self.assertEqual(256 self.assertEqual(
@@ -274,6 +272,7 @@
274 recipe=recipe, distroseries=series)272 recipe=recipe, distroseries=series)
275 self.factory.makeSourcePackageRecipeBuild(273 self.factory.makeSourcePackageRecipeBuild(
276 requester=requester, distroseries=series)274 requester=requester, distroseries=series)
275
277 def get_recent():276 def get_recent():
278 Store.of(build).flush()277 Store.of(build).flush()
279 return SourcePackageRecipeBuild.getRecentBuilds(278 return SourcePackageRecipeBuild.getRecentBuilds(
@@ -332,11 +331,11 @@
332 body, footer = message.get_payload(decode=True).split('\n-- \n')331 body, footer = message.get_payload(decode=True).split('\n-- \n')
333 self.assertEqual(332 self.assertEqual(
334 'Build person/recipe into ppa for distroseries: Successfully'333 'Build person/recipe into ppa for distroseries: Successfully'
335 ' built.\n', body334 ' built.\n', body)
336 )
337335
338 def test_handleStatusNotifies(self):336 def test_handleStatusNotifies(self):
339 """"handleStatus causes notification, even if OK."""337 """"handleStatus causes notification, even if OK."""
338
340 def prepare_build():339 def prepare_build():
341 queue_record = self.factory.makeSourcePackageRecipeBuildJob()340 queue_record = self.factory.makeSourcePackageRecipeBuildJob()
342 build = queue_record.specific_job.build341 build = queue_record.specific_job.build
@@ -345,6 +344,7 @@
345 slave = WaitingSlave('BuildStatus.OK')344 slave = WaitingSlave('BuildStatus.OK')
346 queue_record.builder.setSlaveForTesting(slave)345 queue_record.builder.setSlaveForTesting(slave)
347 return build346 return build
347
348 def assertNotifyOnce(status, build):348 def assertNotifyOnce(status, build):
349 build.handleStatus(status, None, {'filemap': {}})349 build.handleStatus(status, None, {'filemap': {}})
350 self.assertEqual(1, len(pop_notifications()))350 self.assertEqual(1, len(pop_notifications()))
351351
=== added file 'lib/lp/code/vocabularies/sourcepackagerecipe.py'
--- lib/lp/code/vocabularies/sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/vocabularies/sourcepackagerecipe.py 2010-07-21 09:22:01 +0000
@@ -0,0 +1,38 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Source Package Recipe vocabularies used in the lp/code modules."""
5__metaclass__ = type
6__all__ = [
7 'buildable_distroseries_vocabulary',
8 'target_ppas_vocabulary',
9 ]
10
11from zope.component import getUtility
12from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
13
14from canonical.launchpad.interfaces import ILaunchBag
15from canonical.launchpad.webapp.authorization import check_permission
16from canonical.launchpad.webapp.sorting import sorted_dotted_numbers
17from lp.code.model.sourcepackagerecipe import get_buildable_distroseries_set
18from lp.soyuz.browser.archive import make_archive_vocabulary
19from lp.soyuz.interfaces.archive import IArchiveSet
20
21
22def buildable_distroseries_vocabulary(context):
23 """Return a vocabulary of buildable distroseries."""
24 distros = get_buildable_distroseries_set(getUtility(ILaunchBag).user)
25 terms = sorted_dotted_numbers(
26 [SimpleTerm(distro, distro.id, distro.displayname)
27 for distro in distros],
28 key=lambda term: term.value.version)
29 terms.reverse()
30 return SimpleVocabulary(terms)
31
32
33def target_ppas_vocabulary(context):
34 """Return a vocabulary of ppas that the current user can target."""
35 ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user)
36 return make_archive_vocabulary(
37 ppa for ppa in ppas
38 if check_permission('launchpad.Append', ppa))
039
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-07-17 21:02:33 +0000
+++ lib/lp/testing/factory.py 2010-07-21 09:22:01 +0000
@@ -145,7 +145,7 @@
145from lp.soyuz.interfaces.section import ISectionSet145from lp.soyuz.interfaces.section import ISectionSet
146from lp.soyuz.model.binarypackagename import BinaryPackageName146from lp.soyuz.model.binarypackagename import BinaryPackageName
147from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease147from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease
148from lp.soyuz.model.processor import ProcessorFamily, ProcessorFamilySet148from lp.soyuz.model.processor import ProcessorFamilySet
149from lp.soyuz.model.publishing import (149from lp.soyuz.model.publishing import (
150 BinaryPackagePublishingHistory, SourcePackagePublishingHistory)150 BinaryPackagePublishingHistory, SourcePackagePublishingHistory)
151from lp.testing import (151from lp.testing import (
@@ -1798,6 +1798,15 @@
1798 parser = RecipeParser(self.makeRecipeText(*branches))1798 parser = RecipeParser(self.makeRecipeText(*branches))
1799 return parser.parse()1799 return parser.parse()
18001800
1801 def makeSourcePackageRecipeDistroseries(self, name="warty"):
1802 """Return a supported Distroseries to use with Source Package Recipes.
1803
1804 Ew. This uses sampledata currently, which is the ONLY reason this
1805 method exists: it gives us a migration path away from sampledata.
1806 """
1807 ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
1808 return ubuntu.getSeries(name)
1809
1801 def makeSourcePackageRecipe(self, registrant=None, owner=None,1810 def makeSourcePackageRecipe(self, registrant=None, owner=None,
1802 distroseries=None, name=None,1811 distroseries=None, name=None,
1803 description=None, branches=(),1812 description=None, branches=(),
@@ -1809,10 +1818,7 @@
1809 if owner is None:1818 if owner is None:
1810 owner = self.makePerson()1819 owner = self.makePerson()
1811 if distroseries is None:1820 if distroseries is None:
1812 distroseries = self.makeDistroSeries()1821 distroseries = self.makeSourcePackageRecipeDistroseries()
1813 distroseries.nominatedarchindep = distroseries.newArch(
1814 'i386', ProcessorFamily.get(1), False, owner,
1815 supports_virtualized=True)
18161822
1817 if name is None:1823 if name is None:
1818 name = self.getUniqueString().decode('utf8')1824 name = self.getUniqueString().decode('utf8')
@@ -1903,6 +1909,7 @@
1903 ddd57463774cae9b50e70cd51221281b 185913 ed_0.2.orig.tar.gz1909 ddd57463774cae9b50e70cd51221281b 185913 ed_0.2.orig.tar.gz
1904 f9e1e5f13725f581919e9bfd62272a05 8506 ed_0.2-20.diff.gz1910 f9e1e5f13725f581919e9bfd62272a05 8506 ed_0.2-20.diff.gz
1905 """))1911 """))
1912
1906 class Changes:1913 class Changes:
1907 architectures = ['source']1914 architectures = ['source']
1908 logger = QuietFakeLogger()1915 logger = QuietFakeLogger()