Merge lp:~allenap/launchpad/checkwatches-bulk-reload-bug-572211 into lp:launchpad

Proposed by Gavin Panella on 2010-04-30
Status: Merged
Approved by: Gavin Panella on 2010-05-06
Approved revision: no longer in the source branch.
Merged at revision: 10892
Proposed branch: lp:~allenap/launchpad/checkwatches-bulk-reload-bug-572211
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/storm-bulk-reload-bug-572211
Diff against target: 668 lines (+594/-4)
3 files modified
lib/lp/bugs/scripts/checkwatches/core.py (+5/-0)
lib/lp/bugs/scripts/tests/test_bugimport.py (+9/-4)
lib/lp/code/model/tests/test_sourcepackagerecipe.py (+580/-0)
To merge this branch: bzr merge lp:~allenap/launchpad/checkwatches-bulk-reload-bug-572211
Reviewer Review Type Date Requested Status
Abel Deuring (community) code 2010-04-30 Approve on 2010-04-30
Review via email: mp+24493@code.launchpad.net

Commit message

Use bulk.reload() to improve the performance of checkwatches.

Description of the change

This uses the new bulk.reload() method to improve the performance of checkwatches. See the linked bug and the merge proposal for the prerequisite branch for an explanation how.

To post a comment you must log in.
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/scripts/checkwatches/core.py'
2--- lib/lp/bugs/scripts/checkwatches/core.py 2010-05-11 13:11:43 +0000
3+++ lib/lp/bugs/scripts/checkwatches/core.py 2010-05-17 14:12:39 +0000
4@@ -49,6 +49,7 @@
5 from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
6 from lp.bugs.scripts.checkwatches.utilities import (
7 get_bugwatcherrortype_for_error)
8+from lp.services.database.bulk import reload
9 from lp.services.scripts.base import LaunchpadCronScript
10
11
12@@ -336,6 +337,7 @@
13 other_watches = []
14
15 with self.transaction:
16+ reload(bug_watches)
17 remote_bug_ids = [
18 bug_watch.remotebug for bug_watch in bug_watches]
19
20@@ -344,6 +346,7 @@
21 remote_bug_ids))
22
23 with self.transaction:
24+ reload(bug_watches)
25 for bug_watch in bug_watches:
26 if (remote_products.get(bug_watch.remotebug) in
27 self._syncable_gnome_products):
28@@ -450,6 +453,7 @@
29 batch_size = remotesystem.batch_size
30
31 with self.transaction:
32+ reload(bug_watches)
33 old_bug_watches = set(
34 bug_watch for bug_watch in bug_watches
35 if bug_watch.lastchecked is not None)
36@@ -573,6 +577,7 @@
37 # Remove from the list of bug watches any watch whose remote ID
38 # doesn't appear in the list of IDs to check.
39 with self.transaction:
40+ reload(bug_watches)
41 for bug_watch in list(bug_watches):
42 if bug_watch.remotebug not in remote_ids_to_check:
43 bug_watches.remove(bug_watch)
44
45=== modified file 'lib/lp/bugs/scripts/tests/test_bugimport.py'
46--- lib/lp/bugs/scripts/tests/test_bugimport.py 2010-05-04 16:42:03 +0000
47+++ lib/lp/bugs/scripts/tests/test_bugimport.py 2010-05-17 14:12:39 +0000
48@@ -17,10 +17,10 @@
49
50 from canonical.config import config
51 from canonical.database.sqlbase import cursor
52-from lp.bugs.externalbugtracker import (
53- ExternalBugTracker)
54 from canonical.launchpad.database import BugNotification
55 from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet
56+
57+from lp.bugs.externalbugtracker import ExternalBugTracker
58 from lp.bugs.interfaces.bug import CreateBugParams, IBugSet
59 from lp.bugs.interfaces.bugattachment import BugAttachmentType
60 from lp.bugs.interfaces.bugtask import BugTaskImportance, BugTaskStatus
61@@ -29,7 +29,7 @@
62 from lp.bugs.interfaces.externalbugtracker import UNKNOWN_REMOTE_IMPORTANCE
63 from lp.bugs.scripts import bugimport
64 from lp.bugs.scripts.bugimport import ET
65-from lp.bugs.scripts.checkwatches import CheckwatchesMaster
66+from lp.bugs.scripts.checkwatches import CheckwatchesMaster, core
67 from lp.bugs.scripts.checkwatches.remotebugupdater import RemoteBugUpdater
68 from lp.registry.interfaces.person import IPersonSet, PersonCreationRationale
69 from lp.registry.interfaces.product import IProductSet
70@@ -923,7 +923,12 @@
71 def _updateBugTracker(self, bug_tracker):
72 # Save the current bug tracker, so _getBugWatch can reference it.
73 self.bugtracker = bug_tracker
74- super(TestCheckwatchesMaster, self)._updateBugTracker(bug_tracker)
75+ reload = core.reload
76+ try:
77+ core.reload = lambda objects: objects
78+ super(TestCheckwatchesMaster, self)._updateBugTracker(bug_tracker)
79+ finally:
80+ core.reload = reload
81
82 def _getExternalBugTrackersAndWatches(self, bug_tracker, bug_watches):
83 """See `CheckwatchesMaster`."""
84
85=== added file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
86--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 1970-01-01 00:00:00 +0000
87+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-05-11 14:09:44 +0000
88@@ -0,0 +1,580 @@
89+# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the
90+# GNU Affero General Public License version 3 (see the file LICENSE).
91+
92+"""Tests for the SourcePackageRecipe content type."""
93+
94+from __future__ import with_statement
95+
96+__metaclass__ = type
97+
98+from datetime import datetime
99+import textwrap
100+import unittest
101+
102+from bzrlib.plugins.builder.recipe import RecipeParser
103+
104+from pytz import UTC
105+from storm.locals import Store
106+
107+import transaction
108+from zope.component import getUtility
109+from zope.security.interfaces import Unauthorized
110+from zope.security.proxy import removeSecurityProxy
111+
112+from canonical.testing.layers import DatabaseFunctionalLayer, AppServerLayer
113+
114+from canonical.launchpad.webapp.authorization import check_permission
115+from lp.soyuz.interfaces.archive import (
116+ ArchiveDisabled, ArchivePurpose, CannotUploadToArchive, InvalidPocketForPPA)
117+from lp.buildmaster.interfaces.buildqueue import IBuildQueue
118+from lp.buildmaster.model.buildqueue import BuildQueue
119+from lp.code.interfaces.sourcepackagerecipe import (
120+ ForbiddenInstruction, ISourcePackageRecipe, ISourcePackageRecipeSource,
121+ TooNewRecipeFormat, MINIMAL_RECIPE_TEXT)
122+from lp.code.interfaces.sourcepackagerecipebuild import (
123+ ISourcePackageRecipeBuild, ISourcePackageRecipeBuildJob)
124+from lp.code.model.sourcepackagerecipebuild import (
125+ SourcePackageRecipeBuildJob)
126+from lp.code.model.sourcepackagerecipe import (
127+ NonPPABuildRequest)
128+from lp.registry.interfaces.pocket import PackagePublishingPocket
129+from lp.services.job.interfaces.job import (
130+ IJob, JobStatus)
131+from lp.testing import (
132+ ANONYMOUS, launchpadlib_for, login, login_person, person_logged_in,
133+ TestCaseWithFactory, ws_object)
134+
135+
136+class TestSourcePackageRecipe(TestCaseWithFactory):
137+ """Tests for `SourcePackageRecipe` objects."""
138+
139+ layer = DatabaseFunctionalLayer
140+
141+ def makeSourcePackageRecipeFromBuilderRecipe(self, builder_recipe):
142+ """Make a SourcePackageRecipe from a recipe with arbitrary other data.
143+ """
144+ registrant = self.factory.makePerson()
145+ owner = self.factory.makeTeam(owner=registrant)
146+ distroseries = self.factory.makeDistroSeries()
147+ sourcepackagename = self.factory.makeSourcePackageName()
148+ name = self.factory.getUniqueString(u'recipe-name')
149+ description = self.factory.getUniqueString(u'recipe-description')
150+ return getUtility(ISourcePackageRecipeSource).new(
151+ registrant=registrant, owner=owner, distroseries=[distroseries],
152+ sourcepackagename=sourcepackagename, name=name,
153+ description=description, builder_recipe=builder_recipe)
154+
155+ def test_creation(self):
156+ # The metadata supplied when a SourcePackageRecipe is created is
157+ # present on the new object.
158+ registrant = self.factory.makePerson()
159+ owner = self.factory.makeTeam(owner=registrant)
160+ distroseries = self.factory.makeDistroSeries()
161+ sourcepackagename = self.factory.makeSourcePackageName()
162+ name = self.factory.getUniqueString(u'recipe-name')
163+ description = self.factory.getUniqueString(u'recipe-description')
164+ builder_recipe = self.factory.makeRecipe()
165+ recipe = getUtility(ISourcePackageRecipeSource).new(
166+ registrant=registrant, owner=owner, distroseries=[distroseries],
167+ sourcepackagename=sourcepackagename, name=name,
168+ description=description, builder_recipe=builder_recipe)
169+ self.assertEquals(
170+ (registrant, owner, set([distroseries]), sourcepackagename, name),
171+ (recipe.registrant, recipe.owner, set(recipe.distroseries),
172+ recipe.sourcepackagename, recipe.name))
173+
174+ def test_source_implements_interface(self):
175+ # The SourcePackageRecipe class implements ISourcePackageRecipeSource.
176+ self.assertProvides(
177+ getUtility(ISourcePackageRecipeSource),
178+ ISourcePackageRecipeSource)
179+
180+ def test_recipe_implements_interface(self):
181+ # SourcePackageRecipe objects implement ISourcePackageRecipe.
182+ recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
183+ self.factory.makeRecipe())
184+ self.assertProvides(recipe, ISourcePackageRecipe)
185+
186+ def test_base_branch(self):
187+ # When a recipe is created, we can access its base branch.
188+ branch = self.factory.makeAnyBranch()
189+ builder_recipe = self.factory.makeRecipe(branch)
190+ sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
191+ builder_recipe)
192+ self.assertEquals(branch, sp_recipe.base_branch)
193+
194+ def test_branch_links_created(self):
195+ # When a recipe is created, we can query it for links to the branch
196+ # it references.
197+ branch = self.factory.makeAnyBranch()
198+ builder_recipe = self.factory.makeRecipe(branch)
199+ sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
200+ builder_recipe)
201+ self.assertEquals([branch], list(sp_recipe.getReferencedBranches()))
202+
203+ def test_multiple_branch_links_created(self):
204+ # If a recipe links to more than one branch, getReferencedBranches()
205+ # returns all of them.
206+ branch1 = self.factory.makeAnyBranch()
207+ branch2 = self.factory.makeAnyBranch()
208+ builder_recipe = self.factory.makeRecipe(branch1, branch2)
209+ sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
210+ builder_recipe)
211+ self.assertEquals(
212+ sorted([branch1, branch2]),
213+ sorted(sp_recipe.getReferencedBranches()))
214+
215+ def test_random_user_cant_edit(self):
216+ # An arbitrary user can't set attributes.
217+ branch1 = self.factory.makeAnyBranch()
218+ builder_recipe1 = self.factory.makeRecipe(branch1)
219+ sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
220+ builder_recipe1)
221+ branch2 = self.factory.makeAnyBranch()
222+ builder_recipe2 = self.factory.makeRecipe(branch2)
223+ login_person(self.factory.makePerson())
224+ self.assertRaises(
225+ Unauthorized, setattr, sp_recipe, 'builder_recipe',
226+ builder_recipe2)
227+
228+ def test_set_recipe_text_resets_branch_references(self):
229+ # When the recipe_text is replaced, getReferencedBranches returns
230+ # (only) the branches referenced by the new recipe.
231+ branch1 = self.factory.makeAnyBranch()
232+ builder_recipe1 = self.factory.makeRecipe(branch1)
233+ sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
234+ builder_recipe1)
235+ branch2 = self.factory.makeAnyBranch()
236+ builder_recipe2 = self.factory.makeRecipe(branch2)
237+ login_person(sp_recipe.owner.teamowner)
238+ #import pdb; pdb.set_trace()
239+ sp_recipe.builder_recipe = builder_recipe2
240+ self.assertEquals([branch2], list(sp_recipe.getReferencedBranches()))
241+
242+ def test_rejects_run_command(self):
243+ recipe_text = '''\
244+ # bzr-builder format 0.2 deb-version 0.1-{revno}
245+ %(base)s
246+ run touch test
247+ ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
248+ parser = RecipeParser(textwrap.dedent(recipe_text))
249+ builder_recipe = parser.parse()
250+ self.assertRaises(
251+ ForbiddenInstruction,
252+ self.makeSourcePackageRecipeFromBuilderRecipe, builder_recipe)
253+
254+ def test_run_rejected_without_mangling_recipe(self):
255+ branch1 = self.factory.makeAnyBranch()
256+ builder_recipe1 = self.factory.makeRecipe(branch1)
257+ sp_recipe = self.makeSourcePackageRecipeFromBuilderRecipe(
258+ builder_recipe1)
259+ recipe_text = '''\
260+ # bzr-builder format 0.2 deb-version 0.1-{revno}
261+ %(base)s
262+ run touch test
263+ ''' % dict(base=self.factory.makeAnyBranch().bzr_identity)
264+ parser = RecipeParser(textwrap.dedent(recipe_text))
265+ builder_recipe2 = parser.parse()
266+ login_person(sp_recipe.owner.teamowner)
267+ self.assertRaises(
268+ ForbiddenInstruction, setattr, sp_recipe, 'builder_recipe',
269+ builder_recipe2)
270+ self.assertEquals([branch1], list(sp_recipe.getReferencedBranches()))
271+
272+ def test_reject_newer_formats(self):
273+ builder_recipe = self.factory.makeRecipe()
274+ builder_recipe.format = 0.3
275+ self.assertRaises(
276+ TooNewRecipeFormat,
277+ self.makeSourcePackageRecipeFromBuilderRecipe, builder_recipe)
278+
279+ def test_requestBuild(self):
280+ recipe = self.factory.makeSourcePackageRecipe()
281+ (distroseries,) = list(recipe.distroseries)
282+ ppa = self.factory.makeArchive()
283+ build = recipe.requestBuild(ppa, ppa.owner, distroseries,
284+ PackagePublishingPocket.RELEASE)
285+ self.assertProvides(build, ISourcePackageRecipeBuild)
286+ self.assertEqual(build.archive, ppa)
287+ self.assertEqual(build.distroseries, distroseries)
288+ self.assertEqual(build.requester, ppa.owner)
289+ store = Store.of(build)
290+ store.flush()
291+ build_job = store.find(SourcePackageRecipeBuildJob,
292+ SourcePackageRecipeBuildJob.build_id==build.id).one()
293+ self.assertProvides(build_job, ISourcePackageRecipeBuildJob)
294+ self.assertTrue(build_job.virtualized)
295+ job = build_job.job
296+ self.assertProvides(job, IJob)
297+ self.assertEquals(job.status, JobStatus.WAITING)
298+ build_queue = store.find(BuildQueue, BuildQueue.job==job.id).one()
299+ self.assertProvides(build_queue, IBuildQueue)
300+ self.assertTrue(build_queue.virtualized)
301+
302+ def test_requestBuildRejectsNotPPA(self):
303+ recipe = self.factory.makeSourcePackageRecipe()
304+ not_ppa = self.factory.makeArchive(purpose=ArchivePurpose.PRIMARY)
305+ (distroseries,) = list(recipe.distroseries)
306+ self.assertRaises(NonPPABuildRequest, recipe.requestBuild, not_ppa,
307+ not_ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
308+
309+ def test_requestBuildRejectsNoPermission(self):
310+ recipe = self.factory.makeSourcePackageRecipe()
311+ ppa = self.factory.makeArchive()
312+ requester = self.factory.makePerson()
313+ (distroseries,) = list(recipe.distroseries)
314+ self.assertRaises(CannotUploadToArchive, recipe.requestBuild, ppa,
315+ requester, distroseries, PackagePublishingPocket.RELEASE)
316+
317+ def test_requestBuildRejectsInvalidPocket(self):
318+ recipe = self.factory.makeSourcePackageRecipe()
319+ ppa = self.factory.makeArchive()
320+ (distroseries,) = list(recipe.distroseries)
321+ self.assertRaises(InvalidPocketForPPA, recipe.requestBuild, ppa,
322+ ppa.owner, distroseries, PackagePublishingPocket.BACKPORTS)
323+
324+ def test_requestBuildRejectsDisabledArchive(self):
325+ recipe = self.factory.makeSourcePackageRecipe()
326+ ppa = self.factory.makeArchive()
327+ removeSecurityProxy(ppa).disable()
328+ (distroseries,) = list(recipe.distroseries)
329+ self.assertRaises(ArchiveDisabled, recipe.requestBuild, ppa,
330+ ppa.owner, distroseries, PackagePublishingPocket.RELEASE)
331+
332+ def test_sourcepackagerecipe_description(self):
333+ """Ensure that the SourcePackageRecipe has a proper description."""
334+ description = u'The whoozits and whatzits.'
335+ source_package_recipe = self.factory.makeSourcePackageRecipe(
336+ description=description)
337+ self.assertEqual(description, source_package_recipe.description)
338+
339+ def test_distroseries(self):
340+ """Test that the distroseries behaves as a set."""
341+ recipe = self.factory.makeSourcePackageRecipe()
342+ distroseries = self.factory.makeDistroSeries()
343+ (old_distroseries,) = recipe.distroseries
344+ recipe.distroseries.add(distroseries)
345+ self.assertEqual(
346+ set([distroseries, old_distroseries]), set(recipe.distroseries))
347+ recipe.distroseries.remove(distroseries)
348+ self.assertEqual([old_distroseries], list(recipe.distroseries))
349+ recipe.distroseries.clear()
350+ self.assertEqual([], list(recipe.distroseries))
351+
352+ def test_build_daily(self):
353+ """Test that build_daily behaves as a bool."""
354+ recipe = self.factory.makeSourcePackageRecipe()
355+ self.assertFalse(recipe.build_daily)
356+ login_person(recipe.owner)
357+ recipe.build_daily = True
358+ self.assertTrue(recipe.build_daily)
359+
360+ def test_view_public(self):
361+ """Anyone can view a recipe with public branches."""
362+ owner = self.factory.makePerson()
363+ branch = self.factory.makeAnyBranch(owner=owner)
364+ with person_logged_in(owner):
365+ recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
366+ self.assertTrue(check_permission('launchpad.View', recipe))
367+ with person_logged_in(self.factory.makePerson()):
368+ self.assertTrue(check_permission('launchpad.View', recipe))
369+ self.assertTrue(check_permission('launchpad.View', recipe))
370+
371+ def test_view_private(self):
372+ """Recipes with private branches are restricted."""
373+ owner = self.factory.makePerson()
374+ branch = self.factory.makeAnyBranch(owner=owner, private=True)
375+ with person_logged_in(owner):
376+ recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
377+ self.assertTrue(check_permission('launchpad.View', recipe))
378+ with person_logged_in(self.factory.makePerson()):
379+ self.assertFalse(check_permission('launchpad.View', recipe))
380+ self.assertFalse(check_permission('launchpad.View', recipe))
381+
382+ def test_edit(self):
383+ """Only the owner can edit a sourcepackagerecipe."""
384+ recipe = self.factory.makeSourcePackageRecipe()
385+ self.assertFalse(check_permission('launchpad.Edit', recipe))
386+ with person_logged_in(self.factory.makePerson()):
387+ self.assertFalse(check_permission('launchpad.Edit', recipe))
388+ with person_logged_in(recipe.owner):
389+ self.assertTrue(check_permission('launchpad.Edit', recipe))
390+
391+ def test_destroySelf(self):
392+ """Should destroy associated builds, distroseries, etc."""
393+ # Recipe should have at least one datainstruction.
394+ branches = [self.factory.makeBranch() for count in range(2)]
395+ recipe = self.factory.makeSourcePackageRecipe(branches=branches)
396+ pending_build = self.factory.makeSourcePackageRecipeBuild(
397+ recipe=recipe)
398+ self.factory.makeSourcePackageRecipeBuildJob(
399+ recipe_build=pending_build)
400+ past_build = self.factory.makeSourcePackageRecipeBuild(
401+ recipe=recipe)
402+ self.factory.makeSourcePackageRecipeBuildJob(
403+ recipe_build=past_build)
404+ removeSecurityProxy(past_build).datebuilt = datetime.now(UTC)
405+ recipe.destroySelf()
406+ # Show no database constraints were violated
407+ Store.of(recipe).flush()
408+
409+
410+class TestRecipeBranchRoundTripping(TestCaseWithFactory):
411+
412+ layer = DatabaseFunctionalLayer
413+
414+ def setUp(self):
415+ super(TestRecipeBranchRoundTripping, self).setUp()
416+ self.base_branch = self.factory.makeAnyBranch()
417+ self.nested_branch = self.factory.makeAnyBranch()
418+ self.merged_branch = self.factory.makeAnyBranch()
419+ self.branch_identities = {
420+ 'base': self.base_branch.bzr_identity,
421+ 'nested': self.nested_branch.bzr_identity,
422+ 'merged': self.merged_branch.bzr_identity,
423+ }
424+
425+ def get_recipe(self, recipe_text):
426+ builder_recipe = RecipeParser(textwrap.dedent(recipe_text)).parse()
427+ registrant = self.factory.makePerson()
428+ owner = self.factory.makeTeam(owner=registrant)
429+ distroseries = self.factory.makeDistroSeries()
430+ sourcepackagename = self.factory.makeSourcePackageName()
431+ name = self.factory.getUniqueString(u'recipe-name')
432+ description = self.factory.getUniqueString(u'recipe-description')
433+ recipe = getUtility(ISourcePackageRecipeSource).new(
434+ registrant=registrant, owner=owner, distroseries=[distroseries],
435+ sourcepackagename=sourcepackagename, name=name,
436+ description=description, builder_recipe=builder_recipe)
437+ return recipe.builder_recipe
438+
439+ def check_base_recipe_branch(self, branch, url, revspec=None,
440+ num_child_branches=0, revid=None, deb_version=None):
441+ self.check_recipe_branch(branch, None, url, revspec=revspec,
442+ num_child_branches=num_child_branches, revid=revid)
443+ self.assertEqual(deb_version, branch.deb_version)
444+
445+ def check_recipe_branch(self, branch, name, url, revspec=None,
446+ num_child_branches=0, revid=None):
447+ self.assertEqual(name, branch.name)
448+ self.assertEqual(url, branch.url)
449+ self.assertEqual(revspec, branch.revspec)
450+ self.assertEqual(revid, branch.revid)
451+ self.assertEqual(num_child_branches, len(branch.child_branches))
452+
453+ def test_builds_simplest_recipe(self):
454+ recipe_text = '''\
455+ # bzr-builder format 0.2 deb-version 0.1-{revno}
456+ %(base)s
457+ ''' % self.branch_identities
458+ base_branch = self.get_recipe(recipe_text)
459+ self.check_base_recipe_branch(
460+ base_branch, self.base_branch.bzr_identity,
461+ deb_version='0.1-{revno}')
462+
463+ def test_builds_recipe_with_merge(self):
464+ recipe_text = '''\
465+ # bzr-builder format 0.2 deb-version 0.1-{revno}
466+ %(base)s
467+ merge bar %(merged)s
468+ ''' % self.branch_identities
469+ base_branch = self.get_recipe(recipe_text)
470+ self.check_base_recipe_branch(
471+ base_branch, self.base_branch.bzr_identity, num_child_branches=1,
472+ deb_version='0.1-{revno}')
473+ child_branch, location = base_branch.child_branches[0].as_tuple()
474+ self.assertEqual(None, location)
475+ self.check_recipe_branch(
476+ child_branch, "bar", self.merged_branch.bzr_identity)
477+
478+ def test_builds_recipe_with_nest(self):
479+ recipe_text = '''\
480+ # bzr-builder format 0.2 deb-version 0.1-{revno}
481+ %(base)s
482+ nest bar %(nested)s baz
483+ ''' % self.branch_identities
484+ base_branch = self.get_recipe(recipe_text)
485+ self.check_base_recipe_branch(
486+ base_branch, self.base_branch.bzr_identity, num_child_branches=1,
487+ deb_version='0.1-{revno}')
488+ child_branch, location = base_branch.child_branches[0].as_tuple()
489+ self.assertEqual("baz", location)
490+ self.check_recipe_branch(
491+ child_branch, "bar", self.nested_branch.bzr_identity)
492+
493+ def test_builds_recipe_with_nest_then_merge(self):
494+ recipe_text = '''\
495+ # bzr-builder format 0.2 deb-version 0.1-{revno}
496+ %(base)s
497+ nest bar %(nested)s baz
498+ merge zam %(merged)s
499+ ''' % self.branch_identities
500+ base_branch = self.get_recipe(recipe_text)
501+ self.check_base_recipe_branch(
502+ base_branch, self.base_branch.bzr_identity, num_child_branches=2,
503+ deb_version='0.1-{revno}')
504+ child_branch, location = base_branch.child_branches[0].as_tuple()
505+ self.assertEqual("baz", location)
506+ self.check_recipe_branch(
507+ child_branch, "bar", self.nested_branch.bzr_identity)
508+ child_branch, location = base_branch.child_branches[1].as_tuple()
509+ self.assertEqual(None, location)
510+ self.check_recipe_branch(
511+ child_branch, "zam", self.merged_branch.bzr_identity)
512+
513+ def test_builds_recipe_with_merge_then_nest(self):
514+ recipe_text = '''\
515+ # bzr-builder format 0.2 deb-version 0.1-{revno}
516+ %(base)s
517+ merge zam %(merged)s
518+ nest bar %(nested)s baz
519+ ''' % self.branch_identities
520+ base_branch = self.get_recipe(recipe_text)
521+ self.check_base_recipe_branch(
522+ base_branch, self.base_branch.bzr_identity, num_child_branches=2,
523+ deb_version='0.1-{revno}')
524+ child_branch, location = base_branch.child_branches[0].as_tuple()
525+ self.assertEqual(None, location)
526+ self.check_recipe_branch(
527+ child_branch, "zam", self.merged_branch.bzr_identity)
528+ child_branch, location = base_branch.child_branches[1].as_tuple()
529+ self.assertEqual("baz", location)
530+ self.check_recipe_branch(
531+ child_branch, "bar", self.nested_branch.bzr_identity)
532+
533+ def test_builds_a_merge_in_to_a_nest(self):
534+ recipe_text = '''\
535+ # bzr-builder format 0.2 deb-version 0.1-{revno}
536+ %(base)s
537+ nest bar %(nested)s baz
538+ merge zam %(merged)s
539+ ''' % self.branch_identities
540+ base_branch = self.get_recipe(recipe_text)
541+ self.check_base_recipe_branch(
542+ base_branch, self.base_branch.bzr_identity, num_child_branches=1,
543+ deb_version='0.1-{revno}')
544+ child_branch, location = base_branch.child_branches[0].as_tuple()
545+ self.assertEqual("baz", location)
546+ self.check_recipe_branch(
547+ child_branch, "bar", self.nested_branch.bzr_identity,
548+ num_child_branches=1)
549+ child_branch, location = child_branch.child_branches[0].as_tuple()
550+ self.assertEqual(None, location)
551+ self.check_recipe_branch(
552+ child_branch, "zam", self.merged_branch.bzr_identity)
553+
554+ def tests_builds_nest_into_a_nest(self):
555+ nested2 = self.factory.makeAnyBranch()
556+ self.branch_identities['nested2'] = nested2.bzr_identity
557+ recipe_text = '''\
558+ # bzr-builder format 0.2 deb-version 0.1-{revno}
559+ %(base)s
560+ nest bar %(nested)s baz
561+ nest zam %(nested2)s zoo
562+ ''' % self.branch_identities
563+ base_branch = self.get_recipe(recipe_text)
564+ self.check_base_recipe_branch(
565+ base_branch, self.base_branch.bzr_identity, num_child_branches=1,
566+ deb_version='0.1-{revno}')
567+ child_branch, location = base_branch.child_branches[0].as_tuple()
568+ self.assertEqual("baz", location)
569+ self.check_recipe_branch(
570+ child_branch, "bar", self.nested_branch.bzr_identity,
571+ num_child_branches=1)
572+ child_branch, location = child_branch.child_branches[0].as_tuple()
573+ self.assertEqual("zoo", location)
574+ self.check_recipe_branch(child_branch, "zam", nested2.bzr_identity)
575+
576+ def tests_builds_recipe_with_revspecs(self):
577+ recipe_text = '''\
578+ # bzr-builder format 0.2 deb-version 0.1-{revno}
579+ %(base)s revid:a
580+ nest bar %(nested)s baz tag:b
581+ merge zam %(merged)s 2
582+ ''' % self.branch_identities
583+ base_branch = self.get_recipe(recipe_text)
584+ self.check_base_recipe_branch(
585+ base_branch, self.base_branch.bzr_identity, num_child_branches=2,
586+ revspec="revid:a", deb_version='0.1-{revno}')
587+ instruction = base_branch.child_branches[0]
588+ child_branch = instruction.recipe_branch
589+ location = instruction.nest_path
590+ self.assertEqual("baz", location)
591+ self.check_recipe_branch(
592+ child_branch, "bar", self.nested_branch.bzr_identity,
593+ revspec="tag:b")
594+ child_branch, location = base_branch.child_branches[1].as_tuple()
595+ self.assertEqual(None, location)
596+ self.check_recipe_branch(
597+ child_branch, "zam", self.merged_branch.bzr_identity, revspec="2")
598+
599+
600+class TestWebservice(TestCaseWithFactory):
601+
602+ layer = AppServerLayer
603+
604+ def makeRecipeText(self):
605+ branch = self.factory.makeBranch()
606+ return MINIMAL_RECIPE_TEXT % branch.bzr_identity
607+
608+ def makeRecipe(self, user=None, owner=None, recipe_text=None):
609+ if user is None:
610+ user = self.factory.makePerson()
611+ if owner is None:
612+ owner = user
613+ db_distroseries = self.factory.makeDistroSeries()
614+ if recipe_text is None:
615+ recipe_text = self.makeRecipeText()
616+ launchpad = launchpadlib_for('test', user,
617+ service_root="http://api.launchpad.dev:8085")
618+ login(ANONYMOUS)
619+ distroseries = ws_object(launchpad, db_distroseries)
620+ ws_owner = ws_object(launchpad, owner)
621+ recipe = ws_owner.createRecipe(
622+ name='toaster-1', sourcepackagename='toaster',
623+ description='a recipe', distroseries=[distroseries.self_link],
624+ recipe_text=recipe_text)
625+ # at the moment, distroseries is not exposed in the API.
626+ transaction.commit()
627+ db_recipe = owner.getRecipe(name=u'toaster-1')
628+ self.assertEqual(set([db_distroseries]), set(db_recipe.distroseries))
629+ return recipe, ws_owner, launchpad
630+
631+ def test_createRecipe(self):
632+ """Ensure recipe creation works."""
633+ team = self.factory.makeTeam()
634+ recipe_text = self.makeRecipeText()
635+ recipe, user = self.makeRecipe(user=team.teamowner, owner=team,
636+ recipe_text=recipe_text)[:2]
637+ self.assertEqual(team.name, recipe.owner.name)
638+ self.assertEqual(team.teamowner.name, recipe.registrant.name)
639+ self.assertEqual('toaster-1', recipe.name)
640+ self.assertEqual(recipe_text, recipe.recipe_text)
641+ self.assertEqual('toaster', recipe.sourcepackagename)
642+
643+ def test_recipe_text(self):
644+ recipe_text2 = self.makeRecipeText()
645+ recipe = self.makeRecipe()[0]
646+ recipe.setRecipeText(recipe_text=recipe_text2)
647+ self.assertEqual(recipe_text2, recipe.recipe_text)
648+
649+ def test_getRecipe(self):
650+ """Person.getRecipe returns the named recipe."""
651+ recipe, user = self.makeRecipe()[:-1]
652+ self.assertEqual(recipe, user.getRecipe(name=recipe.name))
653+
654+ def test_requestBuild(self):
655+ """Build requests can be performed."""
656+ person = self.factory.makePerson()
657+ archive = self.factory.makeArchive(owner=person)
658+ distroseries = self.factory.makeDistroSeries()
659+ recipe, user, launchpad = self.makeRecipe(person)
660+ distroseries = ws_object(launchpad, distroseries)
661+ archive = ws_object(launchpad, archive)
662+ recipe.requestBuild(
663+ archive=archive, distroseries=distroseries,
664+ pocket=PackagePublishingPocket.RELEASE.title)
665+
666+
667+def test_suite():
668+ return unittest.TestLoader().loadTestsFromName(__name__)