Merge lp:~michael.nelson/launchpad/656166-expose-dsd-diffs into lp:launchpad/db-devel

Proposed by Michael Nelson
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
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 IDistroSeriesDifferences.

Description of the change

Overview
========
This branch adds the ability to request the generation of package diffs for DistroSeriesDifference objects, and exposes this to the API.

Details
=======
I removed lp.registry.exceptions (which I'd created a few branches ago), after seeing that lp.registry.errors already exists.

Generating the diffs also required adding a base_source_pub attribute to IDSDifference.

Test
====
bin/test -vvm test_distroseriesdifference

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Looks great :)

Three fairly trivial comments.

+1

[1]

+ def test_package_diffs(self):
+ # The package diff urls exposed.
+ ds_diff = self.factory.makeDistroSeriesDifference()
+ naked_dsdiff = removeSecurityProxy(ds_diff)
+ naked_dsdiff.package_diff = self.factory.makePackageDiff(
+ status=PackageDiffStatus.PENDING)
+ naked_dsdiff.parent_package_diff = self.factory.makePackageDiff()
+
+ ws_diff = ws_object(self.factory.makeLaunchpadService(
+ ds_diff.owner), ds_diff)
+
+ self.assertEqual(None, ws_diff.package_diff_url)
+ self.assertTrue(ws_diff.parent_package_diff_url.startswith(
+ 'http://localhost:58000/'))

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_package_diff_url is not None.

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.getPublishedSources(
+ self.source_package_name, version=self.base_version,
+ include_pending=True)
+ return pubs[0]

Could getPublishedSources() ever return an empty list?

[3]

+ base_version = versions.get('base')
+ if base_version:

s/:/ is not None:/

review: Approve
Revision history for this message
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_diffs(self):
> +        # The package diff urls exposed.
> +        ds_diff = self.factory.makeDistroSeriesDifference()
> +        naked_dsdiff = removeSecurityProxy(ds_diff)
> +        naked_dsdiff.package_diff = self.factory.makePackageDiff(
> +            status=PackageDiffStatus.PENDING)
> +        naked_dsdiff.parent_package_diff = self.factory.makePackageDiff()
> +
> +        ws_diff = ws_object(self.factory.makeLaunchpadService(
> +            ds_diff.owner), ds_diff)
> +
> +        self.assertEqual(None, ws_diff.package_diff_url)
> +        self.assertTrue(ws_diff.parent_package_diff_url.startswith(
> +            'http://localhost:58000/'))
>
> 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_package_diff_url is not None.

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_series.getPublishedSources(
> +                self.source_package_name, version=self.base_version,
> +                include_pending=True)
> +            return pubs[0]
>
> Could getPublishedSources() ever return an empty list?

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 getPublishedSources() which only returns
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.get('base')
> +        if base_version:
>
> s/:/ is not None:/

Done. Thanks! Incremental here: http://pastebin.ubuntu.com/508671/

Revision history for this message
Gavin Panella (allenap) wrote :

Cool, thanks for the explanation :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches

to status/vote changes: