Merge lp:~rockstar/launchpad/merge-queues-model into lp:launchpad/db-devel
- merge-queues-model
- Merge into db-devel
Proposed by
Paul Hummer
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Aaron Bentley | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 9912 | ||||
Proposed branch: | lp:~rockstar/launchpad/merge-queues-model | ||||
Merge into: | lp:launchpad/db-devel | ||||
Prerequisite: | lp:~rockstar/launchpad/merge-queues-db | ||||
Diff against target: |
715 lines (+383/-131) 11 files modified
lib/lp/code/enums.py (+0/-39) lib/lp/code/errors.py (+11/-0) lib/lp/code/interfaces/branch.py (+30/-4) lib/lp/code/interfaces/branchmergequeue.py (+94/-0) lib/lp/code/model/branch.py (+24/-10) lib/lp/code/model/branchmergequeue.py (+79/-0) lib/lp/code/model/branchnamespace.py (+0/-1) lib/lp/code/model/tests/test_branch.py (+40/-0) lib/lp/code/model/tests/test_branchmergeproposal.py (+0/-77) lib/lp/code/model/tests/test_branchmergequeue.py (+91/-0) lib/lp/testing/factory.py (+14/-0) |
||||
To merge this branch: | bzr merge lp:~rockstar/launchpad/merge-queues-model | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Aaron Bentley (community) | Approve | ||
Review via email: mp+38762@code.launchpad.net |
Commit message
Description of the change
This branch adds the model code for branch merge queues. In my next branch, I'll need to break up the interfaces, because I'm exposing this in the API, but I thought that, instead of doing that in this pipe, it'd make more sense to do it when needed, as this branch seems to be a pretty good stopping point for the work it provides.
Oh, I was found an enum that should have been removed in the previous branch, but was not. I killed it in this branch since that one had already landed.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/enums.py' |
2 | --- lib/lp/code/enums.py 2010-08-20 20:31:18 +0000 |
3 | +++ lib/lp/code/enums.py 2010-10-20 17:48:56 +0000 |
4 | @@ -7,7 +7,6 @@ |
5 | __all__ = [ |
6 | 'BranchLifecycleStatus', |
7 | 'BranchLifecycleStatusFilter', |
8 | - 'BranchMergeControlStatus', |
9 | 'BranchMergeProposalStatus', |
10 | 'BranchSubscriptionDiffSize', |
11 | 'BranchSubscriptionNotificationLevel', |
12 | @@ -77,44 +76,6 @@ |
13 | ABANDONED = DBItem(80, "Abandoned") |
14 | |
15 | |
16 | -class BranchMergeControlStatus(DBEnumeratedType): |
17 | - """Branch Merge Control Status |
18 | - |
19 | - Does the branch want Launchpad to manage a merge queue, and if it does, |
20 | - how does the branch owner handle removing items from the queue. |
21 | - """ |
22 | - |
23 | - NO_QUEUE = DBItem(1, """ |
24 | - Does not use a merge queue |
25 | - |
26 | - The branch does not use the merge queue managed by Launchpad. Merges |
27 | - are tracked and managed elsewhere. Users will not be able to queue up |
28 | - approved branch merge proposals. |
29 | - """) |
30 | - |
31 | - MANUAL = DBItem(2, """ |
32 | - Manual processing of the merge queue |
33 | - |
34 | - One or more people are responsible for manually processing the queued |
35 | - branch merge proposals. |
36 | - """) |
37 | - |
38 | - ROBOT = DBItem(3, """ |
39 | - A branch merge robot is used to process the merge queue |
40 | - |
41 | - An external application, like PQM, is used to merge in the queued |
42 | - approved proposed merges. |
43 | - """) |
44 | - |
45 | - ROBOT_RESTRICTED = DBItem(4, """ |
46 | - The branch merge robot used to process the queue is in restricted mode |
47 | - |
48 | - When the robot is in restricted mode, normal queued branches are not |
49 | - returned for merging, only those with "Queued for Restricted |
50 | - merging" will be. |
51 | - """) |
52 | - |
53 | - |
54 | class BranchType(DBEnumeratedType): |
55 | """Branch Type |
56 | |
57 | |
58 | === modified file 'lib/lp/code/errors.py' |
59 | --- lib/lp/code/errors.py 2010-09-02 14:30:47 +0000 |
60 | +++ lib/lp/code/errors.py 2010-10-20 17:48:56 +0000 |
61 | @@ -26,6 +26,7 @@ |
62 | 'CodeImportNotInReviewedState', |
63 | 'ClaimReviewFailed', |
64 | 'InvalidBranchMergeProposal', |
65 | + 'InvalidMergeQueueConfig', |
66 | 'InvalidNamespace', |
67 | 'NoLinkedBranch', |
68 | 'NoSuchBranch', |
69 | @@ -291,3 +292,13 @@ |
70 | RecipeBuildException.__init__( |
71 | self, recipe, distroseries, |
72 | 'A build against this distro is not allowed.') |
73 | + |
74 | + |
75 | +class InvalidMergeQueueConfig(Exception): |
76 | + """The config specified is not a valid JSON string.""" |
77 | + |
78 | + webservice_error(400) |
79 | + |
80 | + def __init__(self): |
81 | + message = ('The configuration specified is not a valid JSON string.') |
82 | + Exception.__init__(self, message) |
83 | |
84 | === modified file 'lib/lp/code/interfaces/branch.py' |
85 | --- lib/lp/code/interfaces/branch.py 2010-10-08 04:19:45 +0000 |
86 | +++ lib/lp/code/interfaces/branch.py 2010-10-20 17:48:56 +0000 |
87 | @@ -75,13 +75,13 @@ |
88 | ) |
89 | from lp.code.enums import ( |
90 | BranchLifecycleStatus, |
91 | - BranchMergeControlStatus, |
92 | BranchSubscriptionDiffSize, |
93 | BranchSubscriptionNotificationLevel, |
94 | CodeReviewNotificationLevel, |
95 | UICreatableBranchType, |
96 | ) |
97 | from lp.code.interfaces.branchlookup import IBranchLookup |
98 | +from lp.code.interfaces.branchmergequeue import IBranchMergeQueue |
99 | from lp.code.interfaces.branchtarget import IHasBranchTarget |
100 | from lp.code.interfaces.hasbranches import IHasMergeProposals |
101 | from lp.code.interfaces.hasrecipes import IHasRecipes |
102 | @@ -591,9 +591,6 @@ |
103 | def getStackedBranches(): |
104 | """The branches that are stacked on this one.""" |
105 | |
106 | - def getMergeQueue(): |
107 | - """The proposals that are QUEUED to land on this branch.""" |
108 | - |
109 | def getMainlineBranchRevisions(start_date, end_date=None, |
110 | oldest_first=False): |
111 | """Return the matching mainline branch revision objects. |
112 | @@ -993,6 +990,18 @@ |
113 | required=False, readonly=True, |
114 | vocabulary=ControlFormat)) |
115 | |
116 | + merge_queue = Reference( |
117 | + title=_('Branch Merge Queue'), |
118 | + schema=IBranchMergeQueue, required=False, readonly=True, |
119 | + description=_( |
120 | + "The branch merge queue that manages merges for this branch.")) |
121 | + |
122 | + merge_queue_config = TextLine( |
123 | + title=_('Name'), required=True, constraint=branch_name_validator, |
124 | + description=_( |
125 | + "A JSON string of configuration values to send to a branch" |
126 | + "merge robot.")) |
127 | + |
128 | |
129 | class IBranchEdit(Interface): |
130 | """IBranch attributes that require launchpad.Edit permission.""" |
131 | @@ -1066,6 +1075,23 @@ |
132 | :raise: CannotDeleteBranch if the branch cannot be deleted. |
133 | """ |
134 | |
135 | + def addToQueue(queue): |
136 | + """Add this branch to a specified queue. |
137 | + |
138 | + A branch's merges can be managed by a queue. |
139 | + |
140 | + :param queue: The branch merge queue that will manage the branch. |
141 | + """ |
142 | + |
143 | + def setMergeQueueConfig(config): |
144 | + """Set the merge_queue_config property. |
145 | + |
146 | + A branch can store a JSON string of configuration data for a merge |
147 | + robot to retrieve. |
148 | + |
149 | + :param config: A JSON string of data. |
150 | + """ |
151 | + |
152 | |
153 | class IBranch(IBranchPublic, IBranchView, IBranchEdit, |
154 | IBranchEditableAttributes, IBranchAnyone): |
155 | |
156 | === added file 'lib/lp/code/interfaces/branchmergequeue.py' |
157 | --- lib/lp/code/interfaces/branchmergequeue.py 1970-01-01 00:00:00 +0000 |
158 | +++ lib/lp/code/interfaces/branchmergequeue.py 2010-10-20 17:48:56 +0000 |
159 | @@ -0,0 +1,94 @@ |
160 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
161 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
162 | + |
163 | +"""Branch merge queue interfaces.""" |
164 | + |
165 | +__metaclass__ = type |
166 | + |
167 | +__all__ = [ |
168 | + 'IBranchMergeQueue', |
169 | + 'IBranchMergeQueueSource', |
170 | + ] |
171 | + |
172 | +from lazr.restful.fields import ( |
173 | + CollectionField, |
174 | + Reference, |
175 | + ) |
176 | +from zope.interface import Interface |
177 | +from zope.schema import ( |
178 | + Datetime, |
179 | + Int, |
180 | + Text, |
181 | + TextLine, |
182 | + ) |
183 | + |
184 | +from canonical.launchpad import _ |
185 | +from lp.services.fields import ( |
186 | + PersonChoice, |
187 | + PublicPersonChoice, |
188 | + ) |
189 | + |
190 | + |
191 | +class IBranchMergeQueue(Interface): |
192 | + """An interface for managing branch merges.""" |
193 | + |
194 | + id = Int(title=_('ID'), readonly=True, required=True) |
195 | + |
196 | + registrant = PublicPersonChoice( |
197 | + title=_("The user that registered the branch."), |
198 | + required=True, readonly=True, |
199 | + vocabulary='ValidPersonOrTeam') |
200 | + |
201 | + owner = PersonChoice( |
202 | + title=_('Owner'), |
203 | + required=True, readonly=True, |
204 | + vocabulary='UserTeamsParticipationPlusSelf', |
205 | + description=_("The owner of the merge queue.")) |
206 | + |
207 | + name = TextLine( |
208 | + title=_('Name'), required=True, |
209 | + description=_( |
210 | + "Keep very short, unique, and descriptive, because it will " |
211 | + "be used in URLs. " |
212 | + "Examples: main, devel, release-1.0, gnome-vfs.")) |
213 | + |
214 | + description = Text( |
215 | + title=_('Description'), required=False, |
216 | + description=_( |
217 | + 'A short description of the purpose of this merge queue.')) |
218 | + |
219 | + configuration = TextLine( |
220 | + title=_('Configuration'), required=False, |
221 | + description=_( |
222 | + "A JSON string of configuration values.")) |
223 | + |
224 | + date_created = Datetime( |
225 | + title=_('Date Created'), |
226 | + required=True, |
227 | + readonly=True) |
228 | + |
229 | + branches = CollectionField( |
230 | + title=_('Dependent Branches'), |
231 | + description=_('A collection of branches that this queue manages.'), |
232 | + readonly=True, |
233 | + value_type=Reference(Interface)) |
234 | + |
235 | + def setMergeQueueConfig(config): |
236 | + """Set the JSON string configuration of the merge queue. |
237 | + |
238 | + :param config: A JSON string of configuration values. |
239 | + """ |
240 | + |
241 | + |
242 | +class IBranchMergeQueueSource(Interface): |
243 | + |
244 | + def new(name, owner, registrant, description, configuration, branches): |
245 | + """Create a new IBranchMergeQueue object. |
246 | + |
247 | + :param name: The name of the branch merge queue. |
248 | + :param description: A description of queue. |
249 | + :param configuration: A JSON string of configuration values. |
250 | + :param owner: The owner of the queue. |
251 | + :param registrant: The registrant of the queue. |
252 | + :param branches: A list of branches to add to the queue. |
253 | + """ |
254 | |
255 | === modified file 'lib/lp/code/model/branch.py' |
256 | --- lib/lp/code/model/branch.py 2010-10-18 21:18:03 +0000 |
257 | +++ lib/lp/code/model/branch.py 2010-10-20 17:48:56 +0000 |
258 | @@ -10,6 +10,7 @@ |
259 | ] |
260 | |
261 | from datetime import datetime |
262 | +import simplejson |
263 | |
264 | from bzrlib import urlutils |
265 | from bzrlib.revision import NULL_REVISION |
266 | @@ -31,7 +32,11 @@ |
267 | Or, |
268 | Select, |
269 | ) |
270 | -from storm.locals import AutoReload |
271 | +from storm.locals import ( |
272 | + AutoReload, |
273 | + Int, |
274 | + Reference, |
275 | + ) |
276 | from storm.store import Store |
277 | from zope.component import getUtility |
278 | from zope.event import notify |
279 | @@ -70,7 +75,6 @@ |
280 | ) |
281 | from lp.code.enums import ( |
282 | BranchLifecycleStatus, |
283 | - BranchMergeControlStatus, |
284 | BranchMergeProposalStatus, |
285 | BranchType, |
286 | ) |
287 | @@ -82,6 +86,7 @@ |
288 | BranchTypeError, |
289 | CannotDeleteBranch, |
290 | InvalidBranchMergeProposal, |
291 | + InvalidMergeQueueConfig, |
292 | ) |
293 | from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent |
294 | from lp.code.interfaces.branch import ( |
295 | @@ -475,14 +480,6 @@ |
296 | store = Store.of(self) |
297 | return store.find(Branch, Branch.stacked_on == self) |
298 | |
299 | - def getMergeQueue(self): |
300 | - """See `IBranch`.""" |
301 | - return BranchMergeProposal.select(""" |
302 | - BranchMergeProposal.target_branch = %s AND |
303 | - BranchMergeProposal.queue_status = %s |
304 | - """ % sqlvalues(self, BranchMergeProposalStatus.QUEUED), |
305 | - orderBy="queue_position") |
306 | - |
307 | @property |
308 | def code_is_browseable(self): |
309 | """See `IBranch`.""" |
310 | @@ -1141,6 +1138,23 @@ |
311 | SourcePackageRecipeData) |
312 | return SourcePackageRecipeData.findRecipes(self) |
313 | |
314 | + merge_queue_id = Int(name='merge_queue', allow_none=True) |
315 | + merge_queue = Reference(merge_queue_id, 'BranchMergeQueue.id') |
316 | + |
317 | + merge_queue_config = StringCol(dbName='merge_queue_config') |
318 | + |
319 | + def addToQueue(self, queue): |
320 | + """See `IBranchEdit`.""" |
321 | + self.merge_queue = queue |
322 | + |
323 | + def setMergeQueueConfig(self, config): |
324 | + """See `IBranchEdit`.""" |
325 | + try: |
326 | + simplejson.loads(config) |
327 | + self.merge_queue_config = config |
328 | + except ValueError: # The json string is invalid |
329 | + raise InvalidMergeQueueConfig |
330 | + |
331 | |
332 | class DeletionOperation: |
333 | """Represent an operation to perform as part of branch deletion.""" |
334 | |
335 | === added file 'lib/lp/code/model/branchmergequeue.py' |
336 | --- lib/lp/code/model/branchmergequeue.py 1970-01-01 00:00:00 +0000 |
337 | +++ lib/lp/code/model/branchmergequeue.py 2010-10-20 17:48:56 +0000 |
338 | @@ -0,0 +1,79 @@ |
339 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
340 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
341 | + |
342 | +"""Implementation classes for IBranchMergeQueue, etc.""" |
343 | + |
344 | +__metaclass__ = type |
345 | +__all__ = ['BranchMergeQueue'] |
346 | + |
347 | +import simplejson |
348 | + |
349 | +from storm.locals import ( |
350 | + Int, |
351 | + Reference, |
352 | + Store, |
353 | + Storm, |
354 | + Unicode, |
355 | + ) |
356 | +from zope.interface import ( |
357 | + classProvides, |
358 | + implements, |
359 | + ) |
360 | + |
361 | +from canonical.database.datetimecol import UtcDateTimeCol |
362 | +from lp.code.errors import InvalidMergeQueueConfig |
363 | +from lp.code.interfaces.branchmergequeue import ( |
364 | + IBranchMergeQueue, |
365 | + IBranchMergeQueueSource, |
366 | + ) |
367 | +from lp.code.model.branch import Branch |
368 | + |
369 | + |
370 | +class BranchMergeQueue(Storm): |
371 | + """See `IBranchMergeQueue`.""" |
372 | + |
373 | + __storm_table__ = 'BranchMergeQueue' |
374 | + implements(IBranchMergeQueue) |
375 | + classProvides(IBranchMergeQueueSource) |
376 | + |
377 | + id = Int(primary=True) |
378 | + |
379 | + registrant_id = Int(name='registrant', allow_none=True) |
380 | + registrant = Reference(registrant_id, 'Person.id') |
381 | + |
382 | + owner_id = Int(name='owner', allow_none=True) |
383 | + owner = Reference(owner_id, 'Person.id') |
384 | + |
385 | + name = Unicode(allow_none=False) |
386 | + description = Unicode(allow_none=False) |
387 | + configuration = Unicode(allow_none=False) |
388 | + |
389 | + date_created = UtcDateTimeCol(notNull=True) |
390 | + |
391 | + @property |
392 | + def branches(self): |
393 | + """See `IBranchMergeQueue`.""" |
394 | + return Store.of(self).find( |
395 | + Branch, |
396 | + Branch.merge_queue_id == self.id) |
397 | + |
398 | + def setMergeQueueConfig(self, config): |
399 | + """See `IBranchMergeQueue`.""" |
400 | + try: |
401 | + simplejson.loads(config) |
402 | + self.configuration = config |
403 | + except ValueError: # The config string is not valid JSON |
404 | + raise InvalidMergeQueueConfig |
405 | + |
406 | + @classmethod |
407 | + def new(cls, name, owner, registrant, description=None, |
408 | + configuration=None): |
409 | + """See `IBranchMergeQueueSource`.""" |
410 | + queue = cls() |
411 | + queue.name = name |
412 | + queue.owner = owner |
413 | + queue.registrant = registrant |
414 | + queue.description = description |
415 | + queue.configuration = configuration |
416 | + |
417 | + return queue |
418 | |
419 | === modified file 'lib/lp/code/model/branchnamespace.py' |
420 | --- lib/lp/code/model/branchnamespace.py 2010-10-06 02:14:18 +0000 |
421 | +++ lib/lp/code/model/branchnamespace.py 2010-10-20 17:48:56 +0000 |
422 | @@ -27,7 +27,6 @@ |
423 | ) |
424 | from lp.code.enums import ( |
425 | BranchLifecycleStatus, |
426 | - BranchMergeControlStatus, |
427 | BranchSubscriptionDiffSize, |
428 | BranchSubscriptionNotificationLevel, |
429 | BranchVisibilityRule, |
430 | |
431 | === modified file 'lib/lp/code/model/tests/test_branch.py' |
432 | --- lib/lp/code/model/tests/test_branch.py 2010-10-04 19:50:45 +0000 |
433 | +++ lib/lp/code/model/tests/test_branch.py 2010-10-20 17:48:56 +0000 |
434 | @@ -14,6 +14,7 @@ |
435 | datetime, |
436 | timedelta, |
437 | ) |
438 | +import simplejson |
439 | from unittest import TestLoader |
440 | |
441 | from bzrlib.bzrdir import BzrDir |
442 | @@ -66,6 +67,7 @@ |
443 | BranchTargetError, |
444 | CannotDeleteBranch, |
445 | InvalidBranchMergeProposal, |
446 | + InvalidMergeQueueConfig, |
447 | ) |
448 | from lp.code.interfaces.branch import ( |
449 | DEFAULT_BRANCH_STATUS_IN_LISTING, |
450 | @@ -2704,5 +2706,43 @@ |
451 | self.assertRaises(UnsafeUrlSeen, db_stacked.getBzrBranch) |
452 | |
453 | |
454 | +class TestMergeQueue(TestCaseWithFactory): |
455 | + """Tests for branch merge queue functionality in branches.""" |
456 | + |
457 | + layer = DatabaseFunctionalLayer |
458 | + |
459 | + def test_addToQueue(self): |
460 | + """Test Branch.addToQueue.""" |
461 | + branch = self.factory.makeBranch() |
462 | + queue = self.factory.makeBranchMergeQueue() |
463 | + with person_logged_in(branch.owner): |
464 | + branch.addToQueue(queue) |
465 | + |
466 | + self.assertEqual(branch.merge_queue, queue) |
467 | + |
468 | + def test_setMergeQueueConfig(self): |
469 | + """Test Branch.setMergeQueueConfig.""" |
470 | + branch = self.factory.makeBranch() |
471 | + config = simplejson.dumps({ |
472 | + 'path': '/', |
473 | + 'test': 'make test',}) |
474 | + |
475 | + with person_logged_in(branch.owner): |
476 | + branch.setMergeQueueConfig(config) |
477 | + |
478 | + self.assertEqual(branch.merge_queue_config, config) |
479 | + |
480 | + def test_setMergeQueueConfig_invalid(self): |
481 | + """Test that invalid JSON strings aren't added to the database.""" |
482 | + branch = self.factory.makeBranch() |
483 | + config = 'abc' |
484 | + |
485 | + with person_logged_in(branch.owner): |
486 | + self.assertRaises( |
487 | + InvalidMergeQueueConfig, |
488 | + branch.setMergeQueueConfig, |
489 | + config) |
490 | + |
491 | + |
492 | def test_suite(): |
493 | return TestLoader().loadTestsFromName(__name__) |
494 | |
495 | === modified file 'lib/lp/code/model/tests/test_branchmergeproposal.py' |
496 | --- lib/lp/code/model/tests/test_branchmergeproposal.py 2010-10-04 19:50:45 +0000 |
497 | +++ lib/lp/code/model/tests/test_branchmergeproposal.py 2010-10-20 17:48:56 +0000 |
498 | @@ -558,83 +558,6 @@ |
499 | self.assertIsNot(None, proposal.date_review_requested) |
500 | |
501 | |
502 | -class TestBranchMergeProposalQueueing(TestCase): |
503 | - """Test the enqueueing and dequeueing of merge proposals.""" |
504 | - |
505 | - layer = DatabaseFunctionalLayer |
506 | - |
507 | - def setUp(self): |
508 | - TestCase.setUp(self) |
509 | - login(ANONYMOUS) |
510 | - factory = LaunchpadObjectFactory() |
511 | - owner = factory.makePerson() |
512 | - self.target_branch = factory.makeProductBranch(owner=owner) |
513 | - login(self.target_branch.owner.preferredemail.email) |
514 | - self.proposals = [ |
515 | - factory.makeBranchMergeProposal(self.target_branch) |
516 | - for x in range(4)] |
517 | - |
518 | - def test_empty_target_queue(self): |
519 | - """If there are no proposals targeted to the branch, the queue has |
520 | - nothing in it.""" |
521 | - queued_proposals = list(self.target_branch.getMergeQueue()) |
522 | - self.assertEqual(0, len(queued_proposals), |
523 | - "The initial merge queue should be empty.") |
524 | - |
525 | - def test_single_item_in_queue(self): |
526 | - """Enqueing a proposal makes it visible in the target branch queue.""" |
527 | - proposal = self.proposals[0] |
528 | - proposal.enqueue(self.target_branch.owner, 'some-revision-id') |
529 | - queued_proposals = list(self.target_branch.getMergeQueue()) |
530 | - self.assertEqual(1, len(queued_proposals), |
531 | - "Should have one entry in the queue, got %s." |
532 | - % len(queued_proposals)) |
533 | - |
534 | - def test_queue_ordering(self): |
535 | - """Assert that the queue positions are based on the order the |
536 | - proposals were enqueued.""" |
537 | - enqueued_order = [] |
538 | - for proposal in self.proposals[:-1]: |
539 | - enqueued_order.append(proposal.source_branch.unique_name) |
540 | - proposal.enqueue(self.target_branch.owner, 'some-revision') |
541 | - queued_proposals = list(self.target_branch.getMergeQueue()) |
542 | - queue_order = [proposal.source_branch.unique_name |
543 | - for proposal in queued_proposals] |
544 | - self.assertEqual( |
545 | - enqueued_order, queue_order, |
546 | - "The queue should be in the order they were added. " |
547 | - "Expected %s, got %s" % (enqueued_order, queue_order)) |
548 | - |
549 | - # Move the last one to the front. |
550 | - proposal = queued_proposals[-1] |
551 | - proposal.moveToFrontOfQueue() |
552 | - |
553 | - new_queue_order = enqueued_order[-1:] + enqueued_order[:-1] |
554 | - |
555 | - queued_proposals = list(self.target_branch.getMergeQueue()) |
556 | - queue_order = [proposal.source_branch.unique_name |
557 | - for proposal in queued_proposals] |
558 | - self.assertEqual( |
559 | - new_queue_order, queue_order, |
560 | - "The last should now be at the front. " |
561 | - "Expected %s, got %s" % (new_queue_order, queue_order)) |
562 | - |
563 | - # Remove the proposal from the middle of the queue. |
564 | - proposal = queued_proposals[1] |
565 | - proposal.dequeue() |
566 | - syncUpdate(proposal) |
567 | - |
568 | - del new_queue_order[1] |
569 | - |
570 | - queued_proposals = list(self.target_branch.getMergeQueue()) |
571 | - queue_order = [proposal.source_branch.unique_name |
572 | - for proposal in queued_proposals] |
573 | - self.assertEqual( |
574 | - new_queue_order, queue_order, |
575 | - "There should be only two queued items now. " |
576 | - "Expected %s, got %s" % (new_queue_order, queue_order)) |
577 | - |
578 | - |
579 | class TestCreateCommentNotifications(TestCaseWithFactory): |
580 | """Test the notifications are raised at the right times.""" |
581 | |
582 | |
583 | === added file 'lib/lp/code/model/tests/test_branchmergequeue.py' |
584 | --- lib/lp/code/model/tests/test_branchmergequeue.py 1970-01-01 00:00:00 +0000 |
585 | +++ lib/lp/code/model/tests/test_branchmergequeue.py 2010-10-20 17:48:56 +0000 |
586 | @@ -0,0 +1,91 @@ |
587 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
588 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
589 | + |
590 | +"""Unit tests for methods of BranchMergeQueue.""" |
591 | + |
592 | +from __future__ import with_statement |
593 | + |
594 | +import simplejson |
595 | + |
596 | +from canonical.launchpad.interfaces.lpstorm import IStore |
597 | +from canonical.launchpad.webapp.testing import verifyObject |
598 | +from canonical.testing.layers import DatabaseFunctionalLayer |
599 | +from lp.code.errors import InvalidMergeQueueConfig |
600 | +from lp.code.interfaces.branchmergequeue import IBranchMergeQueue |
601 | +from lp.code.model.branchmergequeue import BranchMergeQueue |
602 | +from lp.testing import ( |
603 | + person_logged_in, |
604 | + TestCaseWithFactory, |
605 | + ) |
606 | + |
607 | + |
608 | +class TestBranchMergeQueueInterface(TestCaseWithFactory): |
609 | + """Test IBranchMergeQueue interface.""" |
610 | + |
611 | + layer = DatabaseFunctionalLayer |
612 | + |
613 | + def test_implements_interface(self): |
614 | + queue = self.factory.makeBranchMergeQueue() |
615 | + IStore(BranchMergeQueue).add(queue) |
616 | + verifyObject(IBranchMergeQueue, queue) |
617 | + |
618 | + |
619 | +class TestBranchMergeQueueSource(TestCaseWithFactory): |
620 | + """Test the methods of IBranchMergeQueueSource.""" |
621 | + |
622 | + layer = DatabaseFunctionalLayer |
623 | + |
624 | + def test_new(self): |
625 | + owner = self.factory.makePerson() |
626 | + name = u'SooperQueue' |
627 | + description = u'This is Sooper Queue' |
628 | + config = unicode(simplejson.dumps({'test': 'make check'})) |
629 | + |
630 | + queue = BranchMergeQueue.new( |
631 | + name, owner, owner, description, config) |
632 | + |
633 | + self.assertEqual(queue.name, name) |
634 | + self.assertEqual(queue.owner, owner) |
635 | + self.assertEqual(queue.registrant, owner) |
636 | + self.assertEqual(queue.description, description) |
637 | + self.assertEqual(queue.configuration, config) |
638 | + |
639 | + |
640 | +class TestBranchMergeQueue(TestCaseWithFactory): |
641 | + """Test the functions of the BranchMergeQueue.""" |
642 | + |
643 | + layer = DatabaseFunctionalLayer |
644 | + |
645 | + def test_branches(self): |
646 | + """Test that a merge queue can get all its managed branches.""" |
647 | + store = IStore(BranchMergeQueue) |
648 | + |
649 | + queue = self.factory.makeBranchMergeQueue() |
650 | + store.add(queue) |
651 | + |
652 | + branch = self.factory.makeBranch() |
653 | + store.add(branch) |
654 | + with person_logged_in(branch.owner): |
655 | + branch.addToQueue(queue) |
656 | + |
657 | + self.assertEqual( |
658 | + list(queue.branches), |
659 | + [branch]) |
660 | + |
661 | + def test_setMergeQueueConfig(self): |
662 | + """Test that the configuration is set properly.""" |
663 | + queue = self.factory.makeBranchMergeQueue() |
664 | + config = unicode(simplejson.dumps({ |
665 | + 'test': 'make test'})) |
666 | + |
667 | + queue.setMergeQueueConfig(config) |
668 | + |
669 | + self.assertEqual(queue.configuration, config) |
670 | + |
671 | + def test_setMergeQueueConfig_invalid_json(self): |
672 | + """Test that invalid json can't be set as the config.""" |
673 | + queue = self.factory.makeBranchMergeQueue() |
674 | + self.assertRaises( |
675 | + InvalidMergeQueueConfig, |
676 | + queue.setMergeQueueConfig, |
677 | + 'abc') |
678 | |
679 | === modified file 'lib/lp/testing/factory.py' |
680 | --- lib/lp/testing/factory.py 2010-10-18 23:19:16 +0000 |
681 | +++ lib/lp/testing/factory.py 2010-10-20 17:48:56 +0000 |
682 | @@ -36,6 +36,7 @@ |
683 | from operator import isSequenceType |
684 | import os |
685 | from random import randint |
686 | +import simplejson |
687 | from StringIO import StringIO |
688 | from textwrap import dedent |
689 | from threading import local |
690 | @@ -146,6 +147,7 @@ |
691 | PreviewDiff, |
692 | StaticDiff, |
693 | ) |
694 | +from lp.code.model.branchmergequeue import BranchMergeQueue |
695 | from lp.codehosting.codeimport.worker import CodeImportSourceDetails |
696 | from lp.hardwaredb.interfaces.hwdb import ( |
697 | HWSubmissionFormat, |
698 | @@ -1096,6 +1098,18 @@ |
699 | namespace = target.getNamespace(owner) |
700 | return namespace.createBranch(branch_type, name, creator) |
701 | |
702 | + def makeBranchMergeQueue(self): |
703 | + """Create a BranchMergeQueue.""" |
704 | + name = unicode(self.getUniqueString('queue')) |
705 | + owner = self.makePerson() |
706 | + description = unicode(self.getUniqueString('queue-description')) |
707 | + configuration = unicode(simplejson.dumps({ |
708 | + self.getUniqueString('key'): self.getUniqueString('value')})) |
709 | + |
710 | + queue = BranchMergeQueue.new( |
711 | + name, owner, owner, description, configuration) |
712 | + return queue |
713 | + |
714 | def enableDefaultStackingForProduct(self, product, branch=None): |
715 | """Give 'product' a default stacked-on branch. |
716 |
Please add an extra blank line before the definition of InvalidMergeQue ueConfig, and make BranchMergeQueu e.new a classmethod, not a staticmethod, so you don't need to repeat the class name.
Otherwise, this is good.