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