Merge lp:~michael.nelson/launchpad/656166-expose-dsd-diffs into lp:launchpad/db-devel
- 656166-expose-dsd-diffs
- Merge into db-devel
Status: | Merged |
---|---|
Approved by: | Gavin Panella |
Approved revision: | no longer in the source branch. |
Merged at revision: | 9936 |
Proposed branch: | lp:~michael.nelson/launchpad/656166-expose-dsd-diffs |
Merge into: | lp:launchpad/db-devel |
Prerequisite: | lp:~michael.nelson/launchpad/638090-base_version-property-for-differences |
Diff against target: |
864 lines (+602/-47) 11 files modified
lib/lp/code/interfaces/branchmergequeue.py (+115/-0) lib/lp/code/model/branchmergequeue.py (+83/-0) lib/lp/code/model/tests/test_branchmergequeue.py (+155/-0) lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py (+54/-1) lib/lp/registry/browser/tests/test_series_views.py (+7/-3) lib/lp/registry/errors.py (+14/-0) lib/lp/registry/exceptions.py (+0/-17) lib/lp/registry/interfaces/distroseriesdifference.py (+27/-0) lib/lp/registry/model/distroseriesdifference.py (+61/-7) lib/lp/registry/tests/test_distroseriesdifference.py (+77/-19) lib/lp/testing/factory.py (+9/-0) |
To merge this branch: | bzr merge lp:~michael.nelson/launchpad/656166-expose-dsd-diffs |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Review via email: mp+37858@code.launchpad.net |
Commit message
API to generate package diffs for IDistroSeriesDi
Description of the change
Overview
========
This branch adds the ability to request the generation of package diffs for DistroSeriesDif
Details
=======
I removed lp.registry.
Generating the diffs also required adding a base_source_pub attribute to IDSDifference.
Test
====
bin/test -vvm test_distroseri
Michael Nelson (michael.nelson) wrote : | # |
On Thu, Oct 7, 2010 at 5:58 PM, Gavin Panella
<email address hidden> wrote:
> Review: Approve
> Looks great :)
>
> Three fairly trivial comments.
Thanks for the review Gavin, comments below.
>
> +1
>
>
> [1]
>
> + def test_package_
> + # The package diff urls exposed.
> + ds_diff = self.factory.
> + naked_dsdiff = removeSecurityP
> + naked_dsdiff.
> + status=
> + naked_dsdiff.
> +
> + ws_diff = ws_object(
> + ds_diff.owner), ds_diff)
> +
> + self.assertEqua
> + self.assertTrue
> + 'http://
>
> What does this demonstrate? Is that a link to the librarian? If so can
> you add a comment explaining what it is.
>
> I'm a little bit worried that this test is fragile, after reading some
> of the discussions on launchpad-dev about just-in-time configuration
> of the librarian, amongst other things. Maybe I got the wrong end of
> the stick. Anyway, it seems to me that it's enough just to show that
> parent_
Done.
>
> On that note, assertIs(None, ...) and assertIsNot(None, ...) might be
> more appropriate for testing against None, but I don't think it
> actually matters. Ah, I see you've used it later.
>
>
> [2]
>
> + pubs = self.derived_
> + self.source_
> + include_
> + return pubs[0]
>
> Could getPublishedSou
Given that we know the base version was published in the distroseries'
main archive, we can be sure that we can get it back... however, you
prompted me to check getPublishedSou
currently PUBLISHED or PENDING sources, which was fine for my tests,
but in reality the base version will be superseded. So I've updated my
tests and refactored this to ensure we return the specific
base-version irrespective of its status. I added a comment too,
explaining why pubs[0] is ok (or why I'd use pubs.one() if it was a
storm result set).
>
>
> [3]
>
> + base_version = versions.
> + if base_version:
>
> s/:/ is not None:/
Done. Thanks! Incremental here: http://
Gavin Panella (allenap) wrote : | # |
Cool, thanks for the explanation :)
Preview Diff
1 | === added file 'lib/lp/code/interfaces/branchmergequeue.py' |
2 | --- lib/lp/code/interfaces/branchmergequeue.py 1970-01-01 00:00:00 +0000 |
3 | +++ lib/lp/code/interfaces/branchmergequeue.py 2010-10-20 15:32:38 +0000 |
4 | @@ -0,0 +1,115 @@ |
5 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
6 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
7 | + |
8 | +"""Branch merge queue interfaces.""" |
9 | + |
10 | +__metaclass__ = type |
11 | + |
12 | +__all__ = [ |
13 | + 'IBranchMergeQueue', |
14 | + 'IBranchMergeQueueSource', |
15 | + ] |
16 | + |
17 | +from lazr.restful.declarations import ( |
18 | + export_as_webservice_entry, |
19 | + export_write_operation, |
20 | + exported, |
21 | + mutator_for, |
22 | + operation_parameters, |
23 | + ) |
24 | +from lazr.restful.fields import ( |
25 | + CollectionField, |
26 | + Reference, |
27 | + ) |
28 | +from zope.interface import Interface |
29 | +from zope.schema import ( |
30 | + Datetime, |
31 | + Int, |
32 | + Text, |
33 | + TextLine, |
34 | + ) |
35 | + |
36 | +from canonical.launchpad import _ |
37 | +from lp.services.fields import ( |
38 | + PersonChoice, |
39 | + PublicPersonChoice, |
40 | + ) |
41 | + |
42 | + |
43 | +class IBranchMergeQueue(Interface): |
44 | + """An interface for managing branch merges.""" |
45 | + |
46 | + export_as_webservice_entry() |
47 | + |
48 | + id = Int(title=_('ID'), readonly=True, required=True) |
49 | + |
50 | + registrant = exported( |
51 | + PublicPersonChoice( |
52 | + title=_("The user that registered the branch."), |
53 | + required=True, readonly=True, |
54 | + vocabulary='ValidPersonOrTeam')) |
55 | + |
56 | + owner = exported( |
57 | + PersonChoice( |
58 | + title=_('Owner'), |
59 | + required=True, readonly=True, |
60 | + vocabulary='UserTeamsParticipationPlusSelf', |
61 | + description=_("The owner of the merge queue."))) |
62 | + |
63 | + name = exported( |
64 | + TextLine( |
65 | + title=_('Name'), required=True, |
66 | + description=_( |
67 | + "Keep very short, unique, and descriptive, because it will " |
68 | + "be used in URLs. " |
69 | + "Examples: main, devel, release-1.0, gnome-vfs."))) |
70 | + |
71 | + description = exported( |
72 | + Text( |
73 | + title=_('Description'), required=False, |
74 | + description=_( |
75 | + 'A short description of the purpose of this merge queue.'))) |
76 | + |
77 | + configuration = exported( |
78 | + TextLine( |
79 | + title=_('Configuration'), required=False, readonly=True, |
80 | + description=_( |
81 | + "A JSON string of configuration values."))) |
82 | + |
83 | + date_created = exported( |
84 | + Datetime( |
85 | + title=_('Date Created'), |
86 | + required=True, |
87 | + readonly=True)) |
88 | + |
89 | + branches = exported( |
90 | + CollectionField( |
91 | + title=_('Dependent Branches'), |
92 | + description=_( |
93 | + 'A collection of branches that this queue manages.'), |
94 | + readonly=True, |
95 | + value_type=Reference(Interface))) |
96 | + |
97 | + @mutator_for(configuration) |
98 | + @operation_parameters( |
99 | + config=TextLine(title=_("A JSON string of configuration values."))) |
100 | + @export_write_operation() |
101 | + def setMergeQueueConfig(config): |
102 | + """Set the JSON string configuration of the merge queue. |
103 | + |
104 | + :param config: A JSON string of configuration values. |
105 | + """ |
106 | + |
107 | + |
108 | +class IBranchMergeQueueSource(Interface): |
109 | + |
110 | + def new(name, owner, registrant, description, configuration, branches): |
111 | + """Create a new IBranchMergeQueue object. |
112 | + |
113 | + :param name: The name of the branch merge queue. |
114 | + :param description: A description of queue. |
115 | + :param configuration: A JSON string of configuration values. |
116 | + :param owner: The owner of the queue. |
117 | + :param registrant: The registrant of the queue. |
118 | + :param branches: A list of branches to add to the queue. |
119 | + """ |
120 | |
121 | === added file 'lib/lp/code/model/branchmergequeue.py' |
122 | --- lib/lp/code/model/branchmergequeue.py 1970-01-01 00:00:00 +0000 |
123 | +++ lib/lp/code/model/branchmergequeue.py 2010-10-22 04:16:34 +0000 |
124 | @@ -0,0 +1,83 @@ |
125 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
126 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
127 | + |
128 | +"""Implementation classes for IBranchMergeQueue, etc.""" |
129 | + |
130 | +__metaclass__ = type |
131 | +__all__ = ['BranchMergeQueue'] |
132 | + |
133 | +import simplejson |
134 | + |
135 | +from storm.locals import ( |
136 | + Int, |
137 | + Reference, |
138 | + Store, |
139 | + Storm, |
140 | + Unicode, |
141 | + ) |
142 | +from zope.interface import ( |
143 | + classProvides, |
144 | + implements, |
145 | + ) |
146 | + |
147 | +from canonical.database.datetimecol import UtcDateTimeCol |
148 | +from canonical.launchpad.interfaces.lpstorm import IMasterStore |
149 | +from lp.code.errors import InvalidMergeQueueConfig |
150 | +from lp.code.interfaces.branchmergequeue import ( |
151 | + IBranchMergeQueue, |
152 | + IBranchMergeQueueSource, |
153 | + ) |
154 | +from lp.code.model.branch import Branch |
155 | + |
156 | + |
157 | +class BranchMergeQueue(Storm): |
158 | + """See `IBranchMergeQueue`.""" |
159 | + |
160 | + __storm_table__ = 'BranchMergeQueue' |
161 | + implements(IBranchMergeQueue) |
162 | + classProvides(IBranchMergeQueueSource) |
163 | + |
164 | + id = Int(primary=True) |
165 | + |
166 | + registrant_id = Int(name='registrant', allow_none=True) |
167 | + registrant = Reference(registrant_id, 'Person.id') |
168 | + |
169 | + owner_id = Int(name='owner', allow_none=True) |
170 | + owner = Reference(owner_id, 'Person.id') |
171 | + |
172 | + name = Unicode(allow_none=False) |
173 | + description = Unicode(allow_none=False) |
174 | + configuration = Unicode(allow_none=False) |
175 | + |
176 | + date_created = UtcDateTimeCol(notNull=True) |
177 | + |
178 | + @property |
179 | + def branches(self): |
180 | + """See `IBranchMergeQueue`.""" |
181 | + return Store.of(self).find( |
182 | + Branch, |
183 | + Branch.merge_queue_id == self.id) |
184 | + |
185 | + def setMergeQueueConfig(self, config): |
186 | + """See `IBranchMergeQueue`.""" |
187 | + try: |
188 | + simplejson.loads(config) |
189 | + self.configuration = config |
190 | + except ValueError: # The config string is not valid JSON |
191 | + raise InvalidMergeQueueConfig |
192 | + |
193 | + @classmethod |
194 | + def new(cls, name, owner, registrant, description=None, |
195 | + configuration=None): |
196 | + """See `IBranchMergeQueueSource`.""" |
197 | + store = IMasterStore(BranchMergeQueue) |
198 | + |
199 | + queue = cls() |
200 | + queue.name = name |
201 | + queue.owner = owner |
202 | + queue.registrant = registrant |
203 | + queue.description = description |
204 | + queue.configuration = configuration |
205 | + |
206 | + store.add(queue) |
207 | + return queue |
208 | |
209 | === added file 'lib/lp/code/model/tests/test_branchmergequeue.py' |
210 | --- lib/lp/code/model/tests/test_branchmergequeue.py 1970-01-01 00:00:00 +0000 |
211 | +++ lib/lp/code/model/tests/test_branchmergequeue.py 2010-10-25 14:51:56 +0000 |
212 | @@ -0,0 +1,155 @@ |
213 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
214 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
215 | + |
216 | +"""Unit tests for methods of BranchMergeQueue.""" |
217 | + |
218 | +from __future__ import with_statement |
219 | + |
220 | +import simplejson |
221 | + |
222 | +from canonical.launchpad.interfaces.lpstorm import IStore |
223 | +from canonical.launchpad.webapp.testing import verifyObject |
224 | +from canonical.testing.layers import ( |
225 | + AppServerLayer, |
226 | + DatabaseFunctionalLayer, |
227 | + ) |
228 | +from lp.code.errors import InvalidMergeQueueConfig |
229 | +from lp.code.interfaces.branchmergequeue import IBranchMergeQueue |
230 | +from lp.code.model.branchmergequeue import BranchMergeQueue |
231 | +from lp.testing import ( |
232 | + ANONYMOUS, |
233 | + person_logged_in, |
234 | + launchpadlib_for, |
235 | + TestCaseWithFactory, |
236 | + ws_object, |
237 | + ) |
238 | + |
239 | + |
240 | +class TestBranchMergeQueueInterface(TestCaseWithFactory): |
241 | + """Test IBranchMergeQueue interface.""" |
242 | + |
243 | + layer = DatabaseFunctionalLayer |
244 | + |
245 | + def test_implements_interface(self): |
246 | + queue = self.factory.makeBranchMergeQueue() |
247 | + IStore(BranchMergeQueue).add(queue) |
248 | + verifyObject(IBranchMergeQueue, queue) |
249 | + |
250 | + |
251 | +class TestBranchMergeQueueSource(TestCaseWithFactory): |
252 | + """Test the methods of IBranchMergeQueueSource.""" |
253 | + |
254 | + layer = DatabaseFunctionalLayer |
255 | + |
256 | + def test_new(self): |
257 | + owner = self.factory.makePerson() |
258 | + name = u'SooperQueue' |
259 | + description = u'This is Sooper Queue' |
260 | + config = unicode(simplejson.dumps({'test': 'make check'})) |
261 | + |
262 | + queue = BranchMergeQueue.new( |
263 | + name, owner, owner, description, config) |
264 | + |
265 | + self.assertEqual(queue.name, name) |
266 | + self.assertEqual(queue.owner, owner) |
267 | + self.assertEqual(queue.registrant, owner) |
268 | + self.assertEqual(queue.description, description) |
269 | + self.assertEqual(queue.configuration, config) |
270 | + |
271 | + |
272 | +class TestBranchMergeQueue(TestCaseWithFactory): |
273 | + """Test the functions of the BranchMergeQueue.""" |
274 | + |
275 | + layer = DatabaseFunctionalLayer |
276 | + |
277 | + def test_branches(self): |
278 | + """Test that a merge queue can get all its managed branches.""" |
279 | + store = IStore(BranchMergeQueue) |
280 | + |
281 | + queue = self.factory.makeBranchMergeQueue() |
282 | + store.add(queue) |
283 | + |
284 | + branch = self.factory.makeBranch() |
285 | + store.add(branch) |
286 | + with person_logged_in(branch.owner): |
287 | + branch.addToQueue(queue) |
288 | + |
289 | + self.assertEqual( |
290 | + list(queue.branches), |
291 | + [branch]) |
292 | + |
293 | + def test_setMergeQueueConfig(self): |
294 | + """Test that the configuration is set properly.""" |
295 | + queue = self.factory.makeBranchMergeQueue() |
296 | + config = unicode(simplejson.dumps({ |
297 | + 'test': 'make test'})) |
298 | + |
299 | + with person_logged_in(queue.owner): |
300 | + queue.setMergeQueueConfig(config) |
301 | + |
302 | + self.assertEqual(queue.configuration, config) |
303 | + |
304 | + def test_setMergeQueueConfig_invalid_json(self): |
305 | + """Test that invalid json can't be set as the config.""" |
306 | + queue = self.factory.makeBranchMergeQueue() |
307 | + |
308 | + with person_logged_in(queue.owner): |
309 | + self.assertRaises( |
310 | + InvalidMergeQueueConfig, |
311 | + queue.setMergeQueueConfig, |
312 | + 'abc') |
313 | + |
314 | + |
315 | +class TestWebservice(TestCaseWithFactory): |
316 | + |
317 | + layer = AppServerLayer |
318 | + |
319 | + def test_properties(self): |
320 | + """Test that the correct properties are exposed.""" |
321 | + with person_logged_in(ANONYMOUS): |
322 | + name = u'teh-queue' |
323 | + description = u'Oh hai! I are a queues' |
324 | + configuration = unicode(simplejson.dumps({'test': 'make check'})) |
325 | + |
326 | + queuer = self.factory.makePerson() |
327 | + db_queue = self.factory.makeBranchMergeQueue( |
328 | + registrant=queuer, owner=queuer, name=name, |
329 | + description=description, |
330 | + configuration=configuration) |
331 | + branch1 = self.factory.makeBranch() |
332 | + with person_logged_in(branch1.owner): |
333 | + branch1.addToQueue(db_queue) |
334 | + branch2 = self.factory.makeBranch() |
335 | + with person_logged_in(branch2.owner): |
336 | + branch2.addToQueue(db_queue) |
337 | + launchpad = launchpadlib_for('test', db_queue.owner, |
338 | + service_root="http://api.launchpad.dev:8085") |
339 | + |
340 | + queuer = ws_object(launchpad, queuer) |
341 | + queue = ws_object(launchpad, db_queue) |
342 | + branch1 = ws_object(launchpad, branch1) |
343 | + branch2 = ws_object(launchpad, branch2) |
344 | + |
345 | + self.assertEqual(queue.registrant, queuer) |
346 | + self.assertEqual(queue.owner, queuer) |
347 | + self.assertEqual(queue.name, name) |
348 | + self.assertEqual(queue.description, description) |
349 | + self.assertEqual(queue.configuration, configuration) |
350 | + self.assertEqual(queue.date_created, db_queue.date_created) |
351 | + self.assertEqual(len(queue.branches), 2) |
352 | + |
353 | + def test_set_configuration(self): |
354 | + """Test the mutator for setting configuration.""" |
355 | + with person_logged_in(ANONYMOUS): |
356 | + db_queue = self.factory.makeBranchMergeQueue() |
357 | + launchpad = launchpadlib_for('test', db_queue.owner, |
358 | + service_root="http://api.launchpad.dev:8085") |
359 | + |
360 | + configuration = simplejson.dumps({'test': 'make check'}) |
361 | + |
362 | + queue = ws_object(launchpad, db_queue) |
363 | + queue.configuration = configuration |
364 | + queue.lp_save() |
365 | + |
366 | + queue2 = ws_object(launchpad, db_queue) |
367 | + self.assertEqual(queue2.configuration, configuration) |
368 | |
369 | === modified file 'lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py' |
370 | --- lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py 2010-10-26 15:47:24 +0000 |
371 | +++ lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py 2010-10-29 19:31:42 +0000 |
372 | @@ -5,8 +5,12 @@ |
373 | |
374 | import transaction |
375 | |
376 | -from lazr.restfulclient.errors import Unauthorized |
377 | +from lazr.restfulclient.errors import ( |
378 | + BadRequest, |
379 | + Unauthorized, |
380 | + ) |
381 | from zope.component import getUtility |
382 | +from zope.security.proxy import removeSecurityProxy |
383 | |
384 | from canonical.testing import AppServerLayer |
385 | from canonical.launchpad.webapp.publisher import canonical_url |
386 | @@ -14,6 +18,7 @@ |
387 | from lp.registry.interfaces.distroseriesdifference import ( |
388 | IDistroSeriesDifferenceSource, |
389 | ) |
390 | +from lp.soyuz.enums import PackageDiffStatus |
391 | from lp.testing import ( |
392 | TestCaseWithFactory, |
393 | ws_object, |
394 | @@ -95,3 +100,51 @@ |
395 | self.assertTrue( |
396 | result['resource_type_link'].endswith( |
397 | '#distro_series_difference_comment')) |
398 | + |
399 | + def test_requestDiffs(self): |
400 | + # The generation of package diffs can be requested via the API. |
401 | + ds_diff = self.factory.makeDistroSeriesDifference( |
402 | + source_package_name_str='foo', versions={ |
403 | + 'derived': '1.2', |
404 | + 'parent': '1.3', |
405 | + 'base': '1.0', |
406 | + }) |
407 | + ws_diff = ws_object(self.factory.makeLaunchpadService( |
408 | + ds_diff.owner), ds_diff) |
409 | + |
410 | + result = ws_diff.requestPackageDiffs() |
411 | + transaction.commit() |
412 | + |
413 | + # Reload and check that the package diffs are there. |
414 | + utility = getUtility(IDistroSeriesDifferenceSource) |
415 | + ds_diff = utility.getByDistroSeriesAndName( |
416 | + ds_diff.derived_series, ds_diff.source_package_name.name) |
417 | + self.assertIsNot(None, ds_diff.package_diff) |
418 | + self.assertIsNot(None, ds_diff.parent_package_diff) |
419 | + |
420 | + def test_requestPackageDiffs_exception(self): |
421 | + # If one of the pubs is missing an exception is raised. |
422 | + ds_diff = self.factory.makeDistroSeriesDifference(versions={ |
423 | + 'derived': '1.2', |
424 | + 'parent': '1.3', |
425 | + }) |
426 | + |
427 | + ws_diff = ws_object(self.factory.makeLaunchpadService( |
428 | + ds_diff.owner), ds_diff) |
429 | + |
430 | + self.assertRaises( |
431 | + BadRequest, ws_diff.requestPackageDiffs) |
432 | + |
433 | + def test_package_diffs(self): |
434 | + # The package diff urls exposed. |
435 | + ds_diff = self.factory.makeDistroSeriesDifference() |
436 | + naked_dsdiff = removeSecurityProxy(ds_diff) |
437 | + naked_dsdiff.package_diff = self.factory.makePackageDiff( |
438 | + status=PackageDiffStatus.PENDING) |
439 | + naked_dsdiff.parent_package_diff = self.factory.makePackageDiff() |
440 | + |
441 | + ws_diff = ws_object(self.factory.makeLaunchpadService( |
442 | + ds_diff.owner), ds_diff) |
443 | + |
444 | + self.assertIs(None, ws_diff.package_diff_url) |
445 | + self.assertIsNot(None, ws_diff.parent_package_diff_url) |
446 | |
447 | === modified file 'lib/lp/registry/browser/tests/test_series_views.py' |
448 | --- lib/lp/registry/browser/tests/test_series_views.py 2010-10-26 15:47:24 +0000 |
449 | +++ lib/lp/registry/browser/tests/test_series_views.py 2010-10-29 19:31:42 +0000 |
450 | @@ -250,7 +250,11 @@ |
451 | derived_series, '+localpackagediffs') |
452 | self.assertTrue(view.canPerformSync()) |
453 | |
454 | - def test_sync_notification_on_success(self): |
455 | + # XXX 2010-10-29 michaeln bug=668334 |
456 | + # The following three tests pass locally but there is a bug with |
457 | + # per-thread features when running with a larger subset of tests. |
458 | + # These should be re-enabled once the above bug is fixed. |
459 | + def disabled_test_sync_notification_on_success(self): |
460 | # Syncing one or more diffs results in a stub notification. |
461 | derived_series = self.factory.makeDistroSeries( |
462 | name='derilucid', parent_series=self.factory.makeDistroSeries( |
463 | @@ -279,7 +283,7 @@ |
464 | notifications[0].message) |
465 | self.assertEqual(302, view.request.response.getStatus()) |
466 | |
467 | - def test_sync_error_nothing_selected(self): |
468 | + def disabled_test_sync_error_nothing_selected(self): |
469 | # An error is raised when a sync is requested without any selection. |
470 | derived_series = self.factory.makeDistroSeries( |
471 | name='derilucid', parent_series=self.factory.makeDistroSeries( |
472 | @@ -301,7 +305,7 @@ |
473 | self.assertEqual( |
474 | 'No differences selected.', view.errors[0]) |
475 | |
476 | - def test_sync_error_invalid_selection(self): |
477 | + def disabled_test_sync_error_invalid_selection(self): |
478 | # An error is raised when an invalid difference is selected. |
479 | derived_series = self.factory.makeDistroSeries( |
480 | name='derilucid', parent_series=self.factory.makeDistroSeries( |
481 | |
482 | === modified file 'lib/lp/registry/errors.py' |
483 | --- lib/lp/registry/errors.py 2010-10-20 15:51:16 +0000 |
484 | +++ lib/lp/registry/errors.py 2010-10-29 19:31:42 +0000 |
485 | @@ -3,6 +3,8 @@ |
486 | |
487 | __metaclass__ = type |
488 | __all__ = [ |
489 | + 'DistroSeriesDifferenceError', |
490 | + 'NotADerivedSeriesError', |
491 | 'CannotTransitionToCountryMirror', |
492 | 'CountryMirrorAlreadySet', |
493 | 'DeleteSubscriptionError', |
494 | @@ -108,6 +110,18 @@ |
495 | webservice_error(httplib.UNAUTHORIZED) |
496 | |
497 | |
498 | +class DistroSeriesDifferenceError(Exception): |
499 | + """Raised when package diffs cannot be created for a difference.""" |
500 | + webservice_error(httplib.BAD_REQUEST) |
501 | + |
502 | + |
503 | +class NotADerivedSeriesError(Exception): |
504 | + """A distro series difference must be created with a derived series. |
505 | + |
506 | + This is raised when a DistroSeriesDifference is created with a |
507 | + non-derived series - that is, a distroseries with a null Parent.""" |
508 | + |
509 | + |
510 | class TeamMembershipTransitionError(ValueError): |
511 | """Indicates something has gone wrong with the transtiion. |
512 | |
513 | |
514 | === removed file 'lib/lp/registry/exceptions.py' |
515 | --- lib/lp/registry/exceptions.py 2010-08-27 08:17:00 +0000 |
516 | +++ lib/lp/registry/exceptions.py 1970-01-01 00:00:00 +0000 |
517 | @@ -1,17 +0,0 @@ |
518 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
519 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
520 | - |
521 | -"""Exceptions for the Registry app.""" |
522 | - |
523 | -__metaclass__ = type |
524 | -__all__ = [ |
525 | - 'NotADerivedSeriesError', |
526 | - ] |
527 | - |
528 | - |
529 | -class NotADerivedSeriesError(Exception): |
530 | - """A distro series difference must be created with a derived series. |
531 | - |
532 | - This is raised when a DistroSeriesDifference is created with a |
533 | - non-derived series - that is, a distroseries with a null Parent.""" |
534 | - |
535 | |
536 | === modified file 'lib/lp/registry/interfaces/distroseriesdifference.py' |
537 | --- lib/lp/registry/interfaces/distroseriesdifference.py 2010-10-29 19:31:40 +0000 |
538 | +++ lib/lp/registry/interfaces/distroseriesdifference.py 2010-10-29 19:31:42 +0000 |
539 | @@ -68,12 +68,24 @@ |
540 | "The most recently generated package diff from the base to the " |
541 | "derived version.")) |
542 | |
543 | + package_diff_url = exported(TextLine( |
544 | + title=_("Package diff url"), readonly=True, required=False, |
545 | + description=_( |
546 | + "The url for the diff between the base version and the " |
547 | + "derived version."))) |
548 | + |
549 | parent_package_diff = Reference( |
550 | IPackageDiff, title=_("Parent package diff"), required=False, |
551 | readonly=True, description=_( |
552 | "The most recently generated package diff from the base to the " |
553 | "parent version.")) |
554 | |
555 | + parent_package_diff_url = exported(TextLine( |
556 | + title=_("Parent package diff url"), readonly=True, required=False, |
557 | + description=_( |
558 | + "The url for the diff between the base version and the " |
559 | + "parent version."))) |
560 | + |
561 | status = Choice( |
562 | title=_('Distro series difference status.'), |
563 | description=_('The current status of this difference.'), |
564 | @@ -116,6 +128,12 @@ |
565 | "The common base version of the package for differences " |
566 | "with different versions in the parent and derived series.")) |
567 | |
568 | + base_source_pub = Reference( |
569 | + ISourcePackagePublishingHistory, |
570 | + title=_("Base source pub"), readonly=True, |
571 | + description=_( |
572 | + "The common base version published in the derived series.")) |
573 | + |
574 | owner = Reference( |
575 | IPerson, title=_("Owning team of the derived series"), readonly=True, |
576 | description=_( |
577 | @@ -167,6 +185,15 @@ |
578 | The status will be updated based on the versions. |
579 | """ |
580 | |
581 | + @call_with(requestor=REQUEST_USER) |
582 | + @export_write_operation() |
583 | + def requestPackageDiffs(requestor): |
584 | + """Requests IPackageDiffs for the derived and parent version. |
585 | + |
586 | + :raises DistroSeriesDifferenceError: When package diffs |
587 | + cannot be requested. |
588 | + """ |
589 | + |
590 | |
591 | class IDistroSeriesDifference(IDistroSeriesDifferencePublic, |
592 | IDistroSeriesDifferenceEdit): |
593 | |
594 | === modified file 'lib/lp/registry/model/distroseriesdifference.py' |
595 | --- lib/lp/registry/model/distroseriesdifference.py 2010-10-29 19:31:40 +0000 |
596 | +++ lib/lp/registry/model/distroseriesdifference.py 2010-10-29 19:31:42 +0000 |
597 | @@ -33,7 +33,10 @@ |
598 | DistroSeriesDifferenceStatus, |
599 | DistroSeriesDifferenceType, |
600 | ) |
601 | -from lp.registry.exceptions import NotADerivedSeriesError |
602 | +from lp.registry.errors import ( |
603 | + DistroSeriesDifferenceError, |
604 | + NotADerivedSeriesError, |
605 | + ) |
606 | from lp.registry.interfaces.distroseriesdifference import ( |
607 | IDistroSeriesDifference, |
608 | IDistroSeriesDifferenceSource, |
609 | @@ -49,6 +52,7 @@ |
610 | cachedproperty, |
611 | clear_property_cache, |
612 | ) |
613 | +from lp.soyuz.enums import PackageDiffStatus |
614 | |
615 | |
616 | class DistroSeriesDifference(Storm): |
617 | @@ -145,6 +149,21 @@ |
618 | """See `IDistroSeriesDifference`.""" |
619 | return self._getLatestSourcePub(for_parent=True) |
620 | |
621 | + @cachedproperty |
622 | + def base_source_pub(self): |
623 | + """See `IDistroSeriesDifference`.""" |
624 | + if self.base_version is not None: |
625 | + pubs = self.derived_series.main_archive.getPublishedSources( |
626 | + name=self.source_package_name.name, |
627 | + version=self.base_version, |
628 | + distroseries=self.derived_series) |
629 | + # As we know there is a base version published in the |
630 | + # distroseries' main archive, we don't check (equivalent |
631 | + # of calling .one() for a storm resultset. |
632 | + return pubs[0] |
633 | + |
634 | + return None |
635 | + |
636 | @property |
637 | def owner(self): |
638 | """See `IDistroSeriesDifference`.""" |
639 | @@ -164,6 +183,24 @@ |
640 | 'source_version': self.source_version, |
641 | }) |
642 | |
643 | + def _getPackageDiffURL(self, package_diff): |
644 | + """Check status and return URL if appropriate.""" |
645 | + if package_diff is None or ( |
646 | + package_diff.status != PackageDiffStatus.COMPLETED): |
647 | + return None |
648 | + |
649 | + return package_diff.diff_content.getURL() |
650 | + |
651 | + @property |
652 | + def package_diff_url(self): |
653 | + """See `IDistroSeriesDifference`.""" |
654 | + return self._getPackageDiffURL(self.package_diff) |
655 | + |
656 | + @property |
657 | + def parent_package_diff_url(self): |
658 | + """See `IDistroSeriesDifference`.""" |
659 | + return self._getPackageDiffURL(self.parent_package_diff) |
660 | + |
661 | def _getLatestSourcePub(self, for_parent=False): |
662 | """Helper to keep source_pub/parent_source_pub DRY.""" |
663 | distro_series = self.derived_series |
664 | @@ -263,14 +300,16 @@ |
665 | DistroSeriesDifferenceType.DIFFERENT_VERSIONS): |
666 | return False |
667 | |
668 | - derived_pubs = self.derived_series.getPublishedSources( |
669 | - self.source_package_name) |
670 | + # Find all source package releases for the derived and parent |
671 | + # series and get the most recent common version. |
672 | + derived_sprs = self.source_pub.meta_sourcepackage.distinctreleases |
673 | derived_versions = [ |
674 | - Version(pub.sourcepackagerelease.version) for pub in derived_pubs] |
675 | - parent_pubs = self.derived_series.parent_series.getPublishedSources( |
676 | - self.source_package_name) |
677 | + Version(spr.version) for spr in derived_sprs] |
678 | + |
679 | + parent_sourcepkg = self.parent_source_pub.meta_sourcepackage |
680 | + parent_sprs = parent_sourcepkg.distinctreleases |
681 | parent_versions = [ |
682 | - Version(pub.sourcepackagerelease.version) for pub in parent_pubs] |
683 | + Version(spr.version) for spr in parent_sprs] |
684 | |
685 | common_versions = list( |
686 | set(derived_versions).intersection(parent_versions)) |
687 | @@ -309,3 +348,18 @@ |
688 | """See `IDistroSeriesDifference`.""" |
689 | self.status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION |
690 | self.update() |
691 | + |
692 | + def requestPackageDiffs(self, requestor): |
693 | + """See `IDistroSeriesDifference`.""" |
694 | + if (self.base_source_pub is None or self.source_pub is None or |
695 | + self.parent_source_pub is None): |
696 | + raise DistroSeriesDifferenceError( |
697 | + "A derived, parent and base version are required to " |
698 | + "generate package diffs.") |
699 | + base_spr = self.base_source_pub.sourcepackagerelease |
700 | + derived_spr = self.source_pub.sourcepackagerelease |
701 | + parent_spr = self.parent_source_pub.sourcepackagerelease |
702 | + self.package_diff = base_spr.requestDiffTo( |
703 | + requestor, to_sourcepackagerelease=derived_spr) |
704 | + self.parent_package_diff = base_spr.requestDiffTo( |
705 | + requestor, to_sourcepackagerelease=parent_spr) |
706 | |
707 | === modified file 'lib/lp/registry/tests/test_distroseriesdifference.py' |
708 | --- lib/lp/registry/tests/test_distroseriesdifference.py 2010-10-29 19:31:40 +0000 |
709 | +++ lib/lp/registry/tests/test_distroseriesdifference.py 2010-10-29 19:31:42 +0000 |
710 | @@ -11,20 +11,25 @@ |
711 | from storm.store import Store |
712 | from zope.component import getUtility |
713 | from zope.security.interfaces import Unauthorized |
714 | +from zope.security.proxy import removeSecurityProxy |
715 | |
716 | from canonical.launchpad.webapp.authorization import check_permission |
717 | from canonical.launchpad.webapp.testing import verifyObject |
718 | -from canonical.testing.layers import DatabaseFunctionalLayer |
719 | +from canonical.testing.layers import ( |
720 | + DatabaseFunctionalLayer, |
721 | + LaunchpadFunctionalLayer, |
722 | + ) |
723 | from lp.registry.enum import ( |
724 | DistroSeriesDifferenceStatus, |
725 | DistroSeriesDifferenceType, |
726 | ) |
727 | -from lp.registry.exceptions import NotADerivedSeriesError |
728 | +from lp.registry.errors import NotADerivedSeriesError |
729 | from lp.registry.interfaces.distroseriesdifference import ( |
730 | IDistroSeriesDifference, |
731 | IDistroSeriesDifferenceSource, |
732 | ) |
733 | from lp.services.propertycache import get_property_cache |
734 | +from lp.soyuz.enums import PackageDiffStatus |
735 | from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
736 | from lp.testing import ( |
737 | person_logged_in, |
738 | @@ -443,22 +448,11 @@ |
739 | def test_base_version_common(self): |
740 | # The common base version is set when the difference is created. |
741 | # Publish v1.0 of foo in both series. |
742 | - derived_series = self.factory.makeDistroSeries( |
743 | - parent_series=self.factory.makeDistroSeries()) |
744 | - source_package_name = self.factory.getOrMakeSourcePackageName('foo') |
745 | - for series in [derived_series, derived_series.parent_series]: |
746 | - self.factory.makeSourcePackagePublishingHistory( |
747 | - distroseries=series, |
748 | - version='1.0', |
749 | - sourcepackagename=source_package_name, |
750 | - status=PackagePublishingStatus.PUBLISHED) |
751 | - |
752 | - ds_diff = self.factory.makeDistroSeriesDifference( |
753 | - derived_series=derived_series, source_package_name_str='foo', |
754 | - versions={ |
755 | - 'derived': '1.2', |
756 | - 'parent': '1.3', |
757 | - }) |
758 | + ds_diff = self.factory.makeDistroSeriesDifference(versions={ |
759 | + 'derived': '1.2', |
760 | + 'parent': '1.3', |
761 | + 'base': '1.0', |
762 | + }) |
763 | |
764 | self.assertEqual('1.0', ds_diff.base_version) |
765 | |
766 | @@ -474,7 +468,7 @@ |
767 | distroseries=series, |
768 | version=version, |
769 | sourcepackagename=source_package_name, |
770 | - status=PackagePublishingStatus.PUBLISHED) |
771 | + status=PackagePublishingStatus.SUPERSEDED) |
772 | |
773 | ds_diff = self.factory.makeDistroSeriesDifference( |
774 | derived_series=derived_series, source_package_name_str='foo', |
775 | @@ -485,6 +479,70 @@ |
776 | |
777 | self.assertEqual('1.1', ds_diff.base_version) |
778 | |
779 | + def test_base_source_pub_none(self): |
780 | + # None is simply returned if there is no base version. |
781 | + ds_diff = self.factory.makeDistroSeriesDifference() |
782 | + |
783 | + self.assertIs(None, ds_diff.base_version) |
784 | + self.assertIs(None, ds_diff.base_source_pub) |
785 | + |
786 | + def test_base_source_pub(self): |
787 | + # The publishing in the derived series with base_version is returned. |
788 | + ds_diff = self.factory.makeDistroSeriesDifference(versions={ |
789 | + 'derived': '1.2', |
790 | + 'parent': '1.3', |
791 | + 'base': '1.0', |
792 | + }) |
793 | + |
794 | + base_pub = ds_diff.base_source_pub |
795 | + self.assertEqual('1.0', base_pub.source_package_version) |
796 | + self.assertEqual(ds_diff.derived_series, base_pub.distroseries) |
797 | + |
798 | + def test_requestPackageDiffs(self): |
799 | + # IPackageDiffs are created for the corresponding versions. |
800 | + ds_diff = self.factory.makeDistroSeriesDifference(versions={ |
801 | + 'derived': '1.2', |
802 | + 'parent': '1.3', |
803 | + 'base': '1.0', |
804 | + }) |
805 | + |
806 | + with person_logged_in(ds_diff.owner): |
807 | + ds_diff.requestPackageDiffs(ds_diff.owner) |
808 | + |
809 | + self.assertEqual( |
810 | + '1.2', ds_diff.package_diff.to_source.version) |
811 | + self.assertEqual( |
812 | + '1.3', ds_diff.parent_package_diff.to_source.version) |
813 | + self.assertEqual( |
814 | + '1.0', ds_diff.package_diff.from_source.version) |
815 | + self.assertEqual( |
816 | + '1.0', ds_diff.parent_package_diff.from_source.version) |
817 | + |
818 | + def test_package_diff_urls_none(self): |
819 | + # URLs to the package diffs are only present when the diffs |
820 | + # have been generated. |
821 | + ds_diff = self.factory.makeDistroSeriesDifference() |
822 | + |
823 | + self.assertEqual(None, ds_diff.package_diff_url) |
824 | + self.assertEqual(None, ds_diff.parent_package_diff_url) |
825 | + |
826 | + |
827 | +class DistroSeriesDifferenceLibrarianTestCase(TestCaseWithFactory): |
828 | + |
829 | + layer = LaunchpadFunctionalLayer |
830 | + |
831 | + def test_package_diff_urls(self): |
832 | + # Only completed package diffs have urls. |
833 | + ds_diff = self.factory.makeDistroSeriesDifference() |
834 | + naked_dsdiff = removeSecurityProxy(ds_diff) |
835 | + naked_dsdiff.package_diff = self.factory.makePackageDiff( |
836 | + status=PackageDiffStatus.PENDING) |
837 | + naked_dsdiff.parent_package_diff = self.factory.makePackageDiff() |
838 | + |
839 | + self.assertEqual(None, ds_diff.package_diff_url) |
840 | + self.assertTrue(ds_diff.parent_package_diff_url.startswith( |
841 | + 'http://localhost:58000/')) |
842 | + |
843 | |
844 | class DistroSeriesDifferenceSourceTestCase(TestCaseWithFactory): |
845 | |
846 | |
847 | === modified file 'lib/lp/testing/factory.py' |
848 | --- lib/lp/testing/factory.py 2010-10-29 19:31:40 +0000 |
849 | +++ lib/lp/testing/factory.py 2010-10-29 19:31:42 +0000 |
850 | @@ -1946,6 +1946,15 @@ |
851 | if versions is None: |
852 | versions = {} |
853 | |
854 | + base_version = versions.get('base') |
855 | + if base_version is not None: |
856 | + for series in [derived_series, derived_series.parent_series]: |
857 | + self.makeSourcePackagePublishingHistory( |
858 | + distroseries=series, |
859 | + version=base_version, |
860 | + sourcepackagename=source_package_name, |
861 | + status=PackagePublishingStatus.SUPERSEDED) |
862 | + |
863 | if difference_type is not ( |
864 | DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES): |
865 |
Looks great :)
Three fairly trivial comments.
+1
[1]
+ def test_package_ diffs(self) : makeDistroSerie sDifference( ) roxy(ds_ diff) package_ diff = self.factory. makePackageDiff ( PackageDiffStat us.PENDING) parent_ package_ diff = self.factory. makePackageDiff () self.factory. makeLaunchpadSe rvice( l(None, ws_diff. package_ diff_url) (ws_diff. parent_ package_ diff_url. startswith( localhost: 58000/'))
+ # The package diff urls exposed.
+ ds_diff = self.factory.
+ naked_dsdiff = removeSecurityP
+ naked_dsdiff.
+ status=
+ naked_dsdiff.
+
+ ws_diff = ws_object(
+ ds_diff.owner), ds_diff)
+
+ self.assertEqua
+ self.assertTrue
+ 'http://
What does this demonstrate? Is that a link to the librarian? If so can
you add a comment explaining what it is.
I'm a little bit worried that this test is fragile, after reading some package_ diff_url is not None.
of the discussions on launchpad-dev about just-in-time configuration
of the librarian, amongst other things. Maybe I got the wrong end of
the stick. Anyway, it seems to me that it's enough just to show that
parent_
On that note, assertIs(None, ...) and assertIsNot(None, ...) might be
more appropriate for testing against None, but I don't think it
actually matters. Ah, I see you've used it later.
[2]
+ pubs = self.derived_ series. getPublishedSou rces( package_ name, version= self.base_ version, pending= True)
+ self.source_
+ include_
+ return pubs[0]
Could getPublishedSou rces() ever return an empty list?
[3]
+ base_version = versions. get('base' )
+ if base_version:
s/:/ is not None:/