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
=== added file 'lib/lp/code/interfaces/branchmergequeue.py'
--- lib/lp/code/interfaces/branchmergequeue.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/interfaces/branchmergequeue.py 2010-10-20 15:32:38 +0000
@@ -0,0 +1,115 @@
1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Branch merge queue interfaces."""
5
6__metaclass__ = type
7
8__all__ = [
9 'IBranchMergeQueue',
10 'IBranchMergeQueueSource',
11 ]
12
13from lazr.restful.declarations import (
14 export_as_webservice_entry,
15 export_write_operation,
16 exported,
17 mutator_for,
18 operation_parameters,
19 )
20from lazr.restful.fields import (
21 CollectionField,
22 Reference,
23 )
24from zope.interface import Interface
25from zope.schema import (
26 Datetime,
27 Int,
28 Text,
29 TextLine,
30 )
31
32from canonical.launchpad import _
33from lp.services.fields import (
34 PersonChoice,
35 PublicPersonChoice,
36 )
37
38
39class IBranchMergeQueue(Interface):
40 """An interface for managing branch merges."""
41
42 export_as_webservice_entry()
43
44 id = Int(title=_('ID'), readonly=True, required=True)
45
46 registrant = exported(
47 PublicPersonChoice(
48 title=_("The user that registered the branch."),
49 required=True, readonly=True,
50 vocabulary='ValidPersonOrTeam'))
51
52 owner = exported(
53 PersonChoice(
54 title=_('Owner'),
55 required=True, readonly=True,
56 vocabulary='UserTeamsParticipationPlusSelf',
57 description=_("The owner of the merge queue.")))
58
59 name = exported(
60 TextLine(
61 title=_('Name'), required=True,
62 description=_(
63 "Keep very short, unique, and descriptive, because it will "
64 "be used in URLs. "
65 "Examples: main, devel, release-1.0, gnome-vfs.")))
66
67 description = exported(
68 Text(
69 title=_('Description'), required=False,
70 description=_(
71 'A short description of the purpose of this merge queue.')))
72
73 configuration = exported(
74 TextLine(
75 title=_('Configuration'), required=False, readonly=True,
76 description=_(
77 "A JSON string of configuration values.")))
78
79 date_created = exported(
80 Datetime(
81 title=_('Date Created'),
82 required=True,
83 readonly=True))
84
85 branches = exported(
86 CollectionField(
87 title=_('Dependent Branches'),
88 description=_(
89 'A collection of branches that this queue manages.'),
90 readonly=True,
91 value_type=Reference(Interface)))
92
93 @mutator_for(configuration)
94 @operation_parameters(
95 config=TextLine(title=_("A JSON string of configuration values.")))
96 @export_write_operation()
97 def setMergeQueueConfig(config):
98 """Set the JSON string configuration of the merge queue.
99
100 :param config: A JSON string of configuration values.
101 """
102
103
104class IBranchMergeQueueSource(Interface):
105
106 def new(name, owner, registrant, description, configuration, branches):
107 """Create a new IBranchMergeQueue object.
108
109 :param name: The name of the branch merge queue.
110 :param description: A description of queue.
111 :param configuration: A JSON string of configuration values.
112 :param owner: The owner of the queue.
113 :param registrant: The registrant of the queue.
114 :param branches: A list of branches to add to the queue.
115 """
0116
=== added file 'lib/lp/code/model/branchmergequeue.py'
--- lib/lp/code/model/branchmergequeue.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/branchmergequeue.py 2010-10-22 04:16:34 +0000
@@ -0,0 +1,83 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Implementation classes for IBranchMergeQueue, etc."""
5
6__metaclass__ = type
7__all__ = ['BranchMergeQueue']
8
9import simplejson
10
11from storm.locals import (
12 Int,
13 Reference,
14 Store,
15 Storm,
16 Unicode,
17 )
18from zope.interface import (
19 classProvides,
20 implements,
21 )
22
23from canonical.database.datetimecol import UtcDateTimeCol
24from canonical.launchpad.interfaces.lpstorm import IMasterStore
25from lp.code.errors import InvalidMergeQueueConfig
26from lp.code.interfaces.branchmergequeue import (
27 IBranchMergeQueue,
28 IBranchMergeQueueSource,
29 )
30from lp.code.model.branch import Branch
31
32
33class BranchMergeQueue(Storm):
34 """See `IBranchMergeQueue`."""
35
36 __storm_table__ = 'BranchMergeQueue'
37 implements(IBranchMergeQueue)
38 classProvides(IBranchMergeQueueSource)
39
40 id = Int(primary=True)
41
42 registrant_id = Int(name='registrant', allow_none=True)
43 registrant = Reference(registrant_id, 'Person.id')
44
45 owner_id = Int(name='owner', allow_none=True)
46 owner = Reference(owner_id, 'Person.id')
47
48 name = Unicode(allow_none=False)
49 description = Unicode(allow_none=False)
50 configuration = Unicode(allow_none=False)
51
52 date_created = UtcDateTimeCol(notNull=True)
53
54 @property
55 def branches(self):
56 """See `IBranchMergeQueue`."""
57 return Store.of(self).find(
58 Branch,
59 Branch.merge_queue_id == self.id)
60
61 def setMergeQueueConfig(self, config):
62 """See `IBranchMergeQueue`."""
63 try:
64 simplejson.loads(config)
65 self.configuration = config
66 except ValueError: # The config string is not valid JSON
67 raise InvalidMergeQueueConfig
68
69 @classmethod
70 def new(cls, name, owner, registrant, description=None,
71 configuration=None):
72 """See `IBranchMergeQueueSource`."""
73 store = IMasterStore(BranchMergeQueue)
74
75 queue = cls()
76 queue.name = name
77 queue.owner = owner
78 queue.registrant = registrant
79 queue.description = description
80 queue.configuration = configuration
81
82 store.add(queue)
83 return queue
084
=== added file 'lib/lp/code/model/tests/test_branchmergequeue.py'
--- lib/lp/code/model/tests/test_branchmergequeue.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/tests/test_branchmergequeue.py 2010-10-25 14:51:56 +0000
@@ -0,0 +1,155 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Unit tests for methods of BranchMergeQueue."""
5
6from __future__ import with_statement
7
8import simplejson
9
10from canonical.launchpad.interfaces.lpstorm import IStore
11from canonical.launchpad.webapp.testing import verifyObject
12from canonical.testing.layers import (
13 AppServerLayer,
14 DatabaseFunctionalLayer,
15 )
16from lp.code.errors import InvalidMergeQueueConfig
17from lp.code.interfaces.branchmergequeue import IBranchMergeQueue
18from lp.code.model.branchmergequeue import BranchMergeQueue
19from lp.testing import (
20 ANONYMOUS,
21 person_logged_in,
22 launchpadlib_for,
23 TestCaseWithFactory,
24 ws_object,
25 )
26
27
28class TestBranchMergeQueueInterface(TestCaseWithFactory):
29 """Test IBranchMergeQueue interface."""
30
31 layer = DatabaseFunctionalLayer
32
33 def test_implements_interface(self):
34 queue = self.factory.makeBranchMergeQueue()
35 IStore(BranchMergeQueue).add(queue)
36 verifyObject(IBranchMergeQueue, queue)
37
38
39class TestBranchMergeQueueSource(TestCaseWithFactory):
40 """Test the methods of IBranchMergeQueueSource."""
41
42 layer = DatabaseFunctionalLayer
43
44 def test_new(self):
45 owner = self.factory.makePerson()
46 name = u'SooperQueue'
47 description = u'This is Sooper Queue'
48 config = unicode(simplejson.dumps({'test': 'make check'}))
49
50 queue = BranchMergeQueue.new(
51 name, owner, owner, description, config)
52
53 self.assertEqual(queue.name, name)
54 self.assertEqual(queue.owner, owner)
55 self.assertEqual(queue.registrant, owner)
56 self.assertEqual(queue.description, description)
57 self.assertEqual(queue.configuration, config)
58
59
60class TestBranchMergeQueue(TestCaseWithFactory):
61 """Test the functions of the BranchMergeQueue."""
62
63 layer = DatabaseFunctionalLayer
64
65 def test_branches(self):
66 """Test that a merge queue can get all its managed branches."""
67 store = IStore(BranchMergeQueue)
68
69 queue = self.factory.makeBranchMergeQueue()
70 store.add(queue)
71
72 branch = self.factory.makeBranch()
73 store.add(branch)
74 with person_logged_in(branch.owner):
75 branch.addToQueue(queue)
76
77 self.assertEqual(
78 list(queue.branches),
79 [branch])
80
81 def test_setMergeQueueConfig(self):
82 """Test that the configuration is set properly."""
83 queue = self.factory.makeBranchMergeQueue()
84 config = unicode(simplejson.dumps({
85 'test': 'make test'}))
86
87 with person_logged_in(queue.owner):
88 queue.setMergeQueueConfig(config)
89
90 self.assertEqual(queue.configuration, config)
91
92 def test_setMergeQueueConfig_invalid_json(self):
93 """Test that invalid json can't be set as the config."""
94 queue = self.factory.makeBranchMergeQueue()
95
96 with person_logged_in(queue.owner):
97 self.assertRaises(
98 InvalidMergeQueueConfig,
99 queue.setMergeQueueConfig,
100 'abc')
101
102
103class TestWebservice(TestCaseWithFactory):
104
105 layer = AppServerLayer
106
107 def test_properties(self):
108 """Test that the correct properties are exposed."""
109 with person_logged_in(ANONYMOUS):
110 name = u'teh-queue'
111 description = u'Oh hai! I are a queues'
112 configuration = unicode(simplejson.dumps({'test': 'make check'}))
113
114 queuer = self.factory.makePerson()
115 db_queue = self.factory.makeBranchMergeQueue(
116 registrant=queuer, owner=queuer, name=name,
117 description=description,
118 configuration=configuration)
119 branch1 = self.factory.makeBranch()
120 with person_logged_in(branch1.owner):
121 branch1.addToQueue(db_queue)
122 branch2 = self.factory.makeBranch()
123 with person_logged_in(branch2.owner):
124 branch2.addToQueue(db_queue)
125 launchpad = launchpadlib_for('test', db_queue.owner,
126 service_root="http://api.launchpad.dev:8085")
127
128 queuer = ws_object(launchpad, queuer)
129 queue = ws_object(launchpad, db_queue)
130 branch1 = ws_object(launchpad, branch1)
131 branch2 = ws_object(launchpad, branch2)
132
133 self.assertEqual(queue.registrant, queuer)
134 self.assertEqual(queue.owner, queuer)
135 self.assertEqual(queue.name, name)
136 self.assertEqual(queue.description, description)
137 self.assertEqual(queue.configuration, configuration)
138 self.assertEqual(queue.date_created, db_queue.date_created)
139 self.assertEqual(len(queue.branches), 2)
140
141 def test_set_configuration(self):
142 """Test the mutator for setting configuration."""
143 with person_logged_in(ANONYMOUS):
144 db_queue = self.factory.makeBranchMergeQueue()
145 launchpad = launchpadlib_for('test', db_queue.owner,
146 service_root="http://api.launchpad.dev:8085")
147
148 configuration = simplejson.dumps({'test': 'make check'})
149
150 queue = ws_object(launchpad, db_queue)
151 queue.configuration = configuration
152 queue.lp_save()
153
154 queue2 = ws_object(launchpad, db_queue)
155 self.assertEqual(queue2.configuration, configuration)
0156
=== modified file 'lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py'
--- lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py 2010-10-26 15:47:24 +0000
+++ lib/lp/registry/browser/tests/test_distroseriesdifference_webservice.py 2010-10-29 19:31:42 +0000
@@ -5,8 +5,12 @@
55
6import transaction6import transaction
77
8from lazr.restfulclient.errors import Unauthorized8from lazr.restfulclient.errors import (
9 BadRequest,
10 Unauthorized,
11 )
9from zope.component import getUtility12from zope.component import getUtility
13from zope.security.proxy import removeSecurityProxy
1014
11from canonical.testing import AppServerLayer15from canonical.testing import AppServerLayer
12from canonical.launchpad.webapp.publisher import canonical_url16from canonical.launchpad.webapp.publisher import canonical_url
@@ -14,6 +18,7 @@
14from lp.registry.interfaces.distroseriesdifference import (18from lp.registry.interfaces.distroseriesdifference import (
15 IDistroSeriesDifferenceSource,19 IDistroSeriesDifferenceSource,
16 )20 )
21from lp.soyuz.enums import PackageDiffStatus
17from lp.testing import (22from lp.testing import (
18 TestCaseWithFactory,23 TestCaseWithFactory,
19 ws_object,24 ws_object,
@@ -95,3 +100,51 @@
95 self.assertTrue(100 self.assertTrue(
96 result['resource_type_link'].endswith(101 result['resource_type_link'].endswith(
97 '#distro_series_difference_comment'))102 '#distro_series_difference_comment'))
103
104 def test_requestDiffs(self):
105 # The generation of package diffs can be requested via the API.
106 ds_diff = self.factory.makeDistroSeriesDifference(
107 source_package_name_str='foo', versions={
108 'derived': '1.2',
109 'parent': '1.3',
110 'base': '1.0',
111 })
112 ws_diff = ws_object(self.factory.makeLaunchpadService(
113 ds_diff.owner), ds_diff)
114
115 result = ws_diff.requestPackageDiffs()
116 transaction.commit()
117
118 # Reload and check that the package diffs are there.
119 utility = getUtility(IDistroSeriesDifferenceSource)
120 ds_diff = utility.getByDistroSeriesAndName(
121 ds_diff.derived_series, ds_diff.source_package_name.name)
122 self.assertIsNot(None, ds_diff.package_diff)
123 self.assertIsNot(None, ds_diff.parent_package_diff)
124
125 def test_requestPackageDiffs_exception(self):
126 # If one of the pubs is missing an exception is raised.
127 ds_diff = self.factory.makeDistroSeriesDifference(versions={
128 'derived': '1.2',
129 'parent': '1.3',
130 })
131
132 ws_diff = ws_object(self.factory.makeLaunchpadService(
133 ds_diff.owner), ds_diff)
134
135 self.assertRaises(
136 BadRequest, ws_diff.requestPackageDiffs)
137
138 def test_package_diffs(self):
139 # The package diff urls exposed.
140 ds_diff = self.factory.makeDistroSeriesDifference()
141 naked_dsdiff = removeSecurityProxy(ds_diff)
142 naked_dsdiff.package_diff = self.factory.makePackageDiff(
143 status=PackageDiffStatus.PENDING)
144 naked_dsdiff.parent_package_diff = self.factory.makePackageDiff()
145
146 ws_diff = ws_object(self.factory.makeLaunchpadService(
147 ds_diff.owner), ds_diff)
148
149 self.assertIs(None, ws_diff.package_diff_url)
150 self.assertIsNot(None, ws_diff.parent_package_diff_url)
98151
=== modified file 'lib/lp/registry/browser/tests/test_series_views.py'
--- lib/lp/registry/browser/tests/test_series_views.py 2010-10-26 15:47:24 +0000
+++ lib/lp/registry/browser/tests/test_series_views.py 2010-10-29 19:31:42 +0000
@@ -250,7 +250,11 @@
250 derived_series, '+localpackagediffs')250 derived_series, '+localpackagediffs')
251 self.assertTrue(view.canPerformSync())251 self.assertTrue(view.canPerformSync())
252252
253 def test_sync_notification_on_success(self):253 # XXX 2010-10-29 michaeln bug=668334
254 # The following three tests pass locally but there is a bug with
255 # per-thread features when running with a larger subset of tests.
256 # These should be re-enabled once the above bug is fixed.
257 def disabled_test_sync_notification_on_success(self):
254 # Syncing one or more diffs results in a stub notification.258 # Syncing one or more diffs results in a stub notification.
255 derived_series = self.factory.makeDistroSeries(259 derived_series = self.factory.makeDistroSeries(
256 name='derilucid', parent_series=self.factory.makeDistroSeries(260 name='derilucid', parent_series=self.factory.makeDistroSeries(
@@ -279,7 +283,7 @@
279 notifications[0].message)283 notifications[0].message)
280 self.assertEqual(302, view.request.response.getStatus())284 self.assertEqual(302, view.request.response.getStatus())
281285
282 def test_sync_error_nothing_selected(self):286 def disabled_test_sync_error_nothing_selected(self):
283 # An error is raised when a sync is requested without any selection.287 # An error is raised when a sync is requested without any selection.
284 derived_series = self.factory.makeDistroSeries(288 derived_series = self.factory.makeDistroSeries(
285 name='derilucid', parent_series=self.factory.makeDistroSeries(289 name='derilucid', parent_series=self.factory.makeDistroSeries(
@@ -301,7 +305,7 @@
301 self.assertEqual(305 self.assertEqual(
302 'No differences selected.', view.errors[0])306 'No differences selected.', view.errors[0])
303307
304 def test_sync_error_invalid_selection(self):308 def disabled_test_sync_error_invalid_selection(self):
305 # An error is raised when an invalid difference is selected.309 # An error is raised when an invalid difference is selected.
306 derived_series = self.factory.makeDistroSeries(310 derived_series = self.factory.makeDistroSeries(
307 name='derilucid', parent_series=self.factory.makeDistroSeries(311 name='derilucid', parent_series=self.factory.makeDistroSeries(
308312
=== modified file 'lib/lp/registry/errors.py'
--- lib/lp/registry/errors.py 2010-10-20 15:51:16 +0000
+++ lib/lp/registry/errors.py 2010-10-29 19:31:42 +0000
@@ -3,6 +3,8 @@
33
4__metaclass__ = type4__metaclass__ = type
5__all__ = [5__all__ = [
6 'DistroSeriesDifferenceError',
7 'NotADerivedSeriesError',
6 'CannotTransitionToCountryMirror',8 'CannotTransitionToCountryMirror',
7 'CountryMirrorAlreadySet',9 'CountryMirrorAlreadySet',
8 'DeleteSubscriptionError',10 'DeleteSubscriptionError',
@@ -108,6 +110,18 @@
108 webservice_error(httplib.UNAUTHORIZED)110 webservice_error(httplib.UNAUTHORIZED)
109111
110112
113class DistroSeriesDifferenceError(Exception):
114 """Raised when package diffs cannot be created for a difference."""
115 webservice_error(httplib.BAD_REQUEST)
116
117
118class NotADerivedSeriesError(Exception):
119 """A distro series difference must be created with a derived series.
120
121 This is raised when a DistroSeriesDifference is created with a
122 non-derived series - that is, a distroseries with a null Parent."""
123
124
111class TeamMembershipTransitionError(ValueError):125class TeamMembershipTransitionError(ValueError):
112 """Indicates something has gone wrong with the transtiion.126 """Indicates something has gone wrong with the transtiion.
113127
114128
=== removed file 'lib/lp/registry/exceptions.py'
--- lib/lp/registry/exceptions.py 2010-08-27 08:17:00 +0000
+++ lib/lp/registry/exceptions.py 1970-01-01 00:00:00 +0000
@@ -1,17 +0,0 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Exceptions for the Registry app."""
5
6__metaclass__ = type
7__all__ = [
8 'NotADerivedSeriesError',
9 ]
10
11
12class NotADerivedSeriesError(Exception):
13 """A distro series difference must be created with a derived series.
14
15 This is raised when a DistroSeriesDifference is created with a
16 non-derived series - that is, a distroseries with a null Parent."""
17
180
=== modified file 'lib/lp/registry/interfaces/distroseriesdifference.py'
--- lib/lp/registry/interfaces/distroseriesdifference.py 2010-10-29 19:31:40 +0000
+++ lib/lp/registry/interfaces/distroseriesdifference.py 2010-10-29 19:31:42 +0000
@@ -68,12 +68,24 @@
68 "The most recently generated package diff from the base to the "68 "The most recently generated package diff from the base to the "
69 "derived version."))69 "derived version."))
7070
71 package_diff_url = exported(TextLine(
72 title=_("Package diff url"), readonly=True, required=False,
73 description=_(
74 "The url for the diff between the base version and the "
75 "derived version.")))
76
71 parent_package_diff = Reference(77 parent_package_diff = Reference(
72 IPackageDiff, title=_("Parent package diff"), required=False,78 IPackageDiff, title=_("Parent package diff"), required=False,
73 readonly=True, description=_(79 readonly=True, description=_(
74 "The most recently generated package diff from the base to the "80 "The most recently generated package diff from the base to the "
75 "parent version."))81 "parent version."))
7682
83 parent_package_diff_url = exported(TextLine(
84 title=_("Parent package diff url"), readonly=True, required=False,
85 description=_(
86 "The url for the diff between the base version and the "
87 "parent version.")))
88
77 status = Choice(89 status = Choice(
78 title=_('Distro series difference status.'),90 title=_('Distro series difference status.'),
79 description=_('The current status of this difference.'),91 description=_('The current status of this difference.'),
@@ -116,6 +128,12 @@
116 "The common base version of the package for differences "128 "The common base version of the package for differences "
117 "with different versions in the parent and derived series."))129 "with different versions in the parent and derived series."))
118130
131 base_source_pub = Reference(
132 ISourcePackagePublishingHistory,
133 title=_("Base source pub"), readonly=True,
134 description=_(
135 "The common base version published in the derived series."))
136
119 owner = Reference(137 owner = Reference(
120 IPerson, title=_("Owning team of the derived series"), readonly=True,138 IPerson, title=_("Owning team of the derived series"), readonly=True,
121 description=_(139 description=_(
@@ -167,6 +185,15 @@
167 The status will be updated based on the versions.185 The status will be updated based on the versions.
168 """186 """
169187
188 @call_with(requestor=REQUEST_USER)
189 @export_write_operation()
190 def requestPackageDiffs(requestor):
191 """Requests IPackageDiffs for the derived and parent version.
192
193 :raises DistroSeriesDifferenceError: When package diffs
194 cannot be requested.
195 """
196
170197
171class IDistroSeriesDifference(IDistroSeriesDifferencePublic,198class IDistroSeriesDifference(IDistroSeriesDifferencePublic,
172 IDistroSeriesDifferenceEdit):199 IDistroSeriesDifferenceEdit):
173200
=== modified file 'lib/lp/registry/model/distroseriesdifference.py'
--- lib/lp/registry/model/distroseriesdifference.py 2010-10-29 19:31:40 +0000
+++ lib/lp/registry/model/distroseriesdifference.py 2010-10-29 19:31:42 +0000
@@ -33,7 +33,10 @@
33 DistroSeriesDifferenceStatus,33 DistroSeriesDifferenceStatus,
34 DistroSeriesDifferenceType,34 DistroSeriesDifferenceType,
35 )35 )
36from lp.registry.exceptions import NotADerivedSeriesError36from lp.registry.errors import (
37 DistroSeriesDifferenceError,
38 NotADerivedSeriesError,
39 )
37from lp.registry.interfaces.distroseriesdifference import (40from lp.registry.interfaces.distroseriesdifference import (
38 IDistroSeriesDifference,41 IDistroSeriesDifference,
39 IDistroSeriesDifferenceSource,42 IDistroSeriesDifferenceSource,
@@ -49,6 +52,7 @@
49 cachedproperty,52 cachedproperty,
50 clear_property_cache,53 clear_property_cache,
51 )54 )
55from lp.soyuz.enums import PackageDiffStatus
5256
5357
54class DistroSeriesDifference(Storm):58class DistroSeriesDifference(Storm):
@@ -145,6 +149,21 @@
145 """See `IDistroSeriesDifference`."""149 """See `IDistroSeriesDifference`."""
146 return self._getLatestSourcePub(for_parent=True)150 return self._getLatestSourcePub(for_parent=True)
147151
152 @cachedproperty
153 def base_source_pub(self):
154 """See `IDistroSeriesDifference`."""
155 if self.base_version is not None:
156 pubs = self.derived_series.main_archive.getPublishedSources(
157 name=self.source_package_name.name,
158 version=self.base_version,
159 distroseries=self.derived_series)
160 # As we know there is a base version published in the
161 # distroseries' main archive, we don't check (equivalent
162 # of calling .one() for a storm resultset.
163 return pubs[0]
164
165 return None
166
148 @property167 @property
149 def owner(self):168 def owner(self):
150 """See `IDistroSeriesDifference`."""169 """See `IDistroSeriesDifference`."""
@@ -164,6 +183,24 @@
164 'source_version': self.source_version,183 'source_version': self.source_version,
165 })184 })
166185
186 def _getPackageDiffURL(self, package_diff):
187 """Check status and return URL if appropriate."""
188 if package_diff is None or (
189 package_diff.status != PackageDiffStatus.COMPLETED):
190 return None
191
192 return package_diff.diff_content.getURL()
193
194 @property
195 def package_diff_url(self):
196 """See `IDistroSeriesDifference`."""
197 return self._getPackageDiffURL(self.package_diff)
198
199 @property
200 def parent_package_diff_url(self):
201 """See `IDistroSeriesDifference`."""
202 return self._getPackageDiffURL(self.parent_package_diff)
203
167 def _getLatestSourcePub(self, for_parent=False):204 def _getLatestSourcePub(self, for_parent=False):
168 """Helper to keep source_pub/parent_source_pub DRY."""205 """Helper to keep source_pub/parent_source_pub DRY."""
169 distro_series = self.derived_series206 distro_series = self.derived_series
@@ -263,14 +300,16 @@
263 DistroSeriesDifferenceType.DIFFERENT_VERSIONS):300 DistroSeriesDifferenceType.DIFFERENT_VERSIONS):
264 return False301 return False
265302
266 derived_pubs = self.derived_series.getPublishedSources(303 # Find all source package releases for the derived and parent
267 self.source_package_name)304 # series and get the most recent common version.
305 derived_sprs = self.source_pub.meta_sourcepackage.distinctreleases
268 derived_versions = [306 derived_versions = [
269 Version(pub.sourcepackagerelease.version) for pub in derived_pubs]307 Version(spr.version) for spr in derived_sprs]
270 parent_pubs = self.derived_series.parent_series.getPublishedSources(308
271 self.source_package_name)309 parent_sourcepkg = self.parent_source_pub.meta_sourcepackage
310 parent_sprs = parent_sourcepkg.distinctreleases
272 parent_versions = [311 parent_versions = [
273 Version(pub.sourcepackagerelease.version) for pub in parent_pubs]312 Version(spr.version) for spr in parent_sprs]
274313
275 common_versions = list(314 common_versions = list(
276 set(derived_versions).intersection(parent_versions))315 set(derived_versions).intersection(parent_versions))
@@ -309,3 +348,18 @@
309 """See `IDistroSeriesDifference`."""348 """See `IDistroSeriesDifference`."""
310 self.status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION349 self.status = DistroSeriesDifferenceStatus.NEEDS_ATTENTION
311 self.update()350 self.update()
351
352 def requestPackageDiffs(self, requestor):
353 """See `IDistroSeriesDifference`."""
354 if (self.base_source_pub is None or self.source_pub is None or
355 self.parent_source_pub is None):
356 raise DistroSeriesDifferenceError(
357 "A derived, parent and base version are required to "
358 "generate package diffs.")
359 base_spr = self.base_source_pub.sourcepackagerelease
360 derived_spr = self.source_pub.sourcepackagerelease
361 parent_spr = self.parent_source_pub.sourcepackagerelease
362 self.package_diff = base_spr.requestDiffTo(
363 requestor, to_sourcepackagerelease=derived_spr)
364 self.parent_package_diff = base_spr.requestDiffTo(
365 requestor, to_sourcepackagerelease=parent_spr)
312366
=== modified file 'lib/lp/registry/tests/test_distroseriesdifference.py'
--- lib/lp/registry/tests/test_distroseriesdifference.py 2010-10-29 19:31:40 +0000
+++ lib/lp/registry/tests/test_distroseriesdifference.py 2010-10-29 19:31:42 +0000
@@ -11,20 +11,25 @@
11from storm.store import Store11from storm.store import Store
12from zope.component import getUtility12from zope.component import getUtility
13from zope.security.interfaces import Unauthorized13from zope.security.interfaces import Unauthorized
14from zope.security.proxy import removeSecurityProxy
1415
15from canonical.launchpad.webapp.authorization import check_permission16from canonical.launchpad.webapp.authorization import check_permission
16from canonical.launchpad.webapp.testing import verifyObject17from canonical.launchpad.webapp.testing import verifyObject
17from canonical.testing.layers import DatabaseFunctionalLayer18from canonical.testing.layers import (
19 DatabaseFunctionalLayer,
20 LaunchpadFunctionalLayer,
21 )
18from lp.registry.enum import (22from lp.registry.enum import (
19 DistroSeriesDifferenceStatus,23 DistroSeriesDifferenceStatus,
20 DistroSeriesDifferenceType,24 DistroSeriesDifferenceType,
21 )25 )
22from lp.registry.exceptions import NotADerivedSeriesError26from lp.registry.errors import NotADerivedSeriesError
23from lp.registry.interfaces.distroseriesdifference import (27from lp.registry.interfaces.distroseriesdifference import (
24 IDistroSeriesDifference,28 IDistroSeriesDifference,
25 IDistroSeriesDifferenceSource,29 IDistroSeriesDifferenceSource,
26 )30 )
27from lp.services.propertycache import get_property_cache31from lp.services.propertycache import get_property_cache
32from lp.soyuz.enums import PackageDiffStatus
28from lp.soyuz.interfaces.publishing import PackagePublishingStatus33from lp.soyuz.interfaces.publishing import PackagePublishingStatus
29from lp.testing import (34from lp.testing import (
30 person_logged_in,35 person_logged_in,
@@ -443,22 +448,11 @@
443 def test_base_version_common(self):448 def test_base_version_common(self):
444 # The common base version is set when the difference is created.449 # The common base version is set when the difference is created.
445 # Publish v1.0 of foo in both series.450 # Publish v1.0 of foo in both series.
446 derived_series = self.factory.makeDistroSeries(451 ds_diff = self.factory.makeDistroSeriesDifference(versions={
447 parent_series=self.factory.makeDistroSeries())452 'derived': '1.2',
448 source_package_name = self.factory.getOrMakeSourcePackageName('foo')453 'parent': '1.3',
449 for series in [derived_series, derived_series.parent_series]:454 'base': '1.0',
450 self.factory.makeSourcePackagePublishingHistory(455 })
451 distroseries=series,
452 version='1.0',
453 sourcepackagename=source_package_name,
454 status=PackagePublishingStatus.PUBLISHED)
455
456 ds_diff = self.factory.makeDistroSeriesDifference(
457 derived_series=derived_series, source_package_name_str='foo',
458 versions={
459 'derived': '1.2',
460 'parent': '1.3',
461 })
462456
463 self.assertEqual('1.0', ds_diff.base_version)457 self.assertEqual('1.0', ds_diff.base_version)
464458
@@ -474,7 +468,7 @@
474 distroseries=series,468 distroseries=series,
475 version=version,469 version=version,
476 sourcepackagename=source_package_name,470 sourcepackagename=source_package_name,
477 status=PackagePublishingStatus.PUBLISHED)471 status=PackagePublishingStatus.SUPERSEDED)
478472
479 ds_diff = self.factory.makeDistroSeriesDifference(473 ds_diff = self.factory.makeDistroSeriesDifference(
480 derived_series=derived_series, source_package_name_str='foo',474 derived_series=derived_series, source_package_name_str='foo',
@@ -485,6 +479,70 @@
485479
486 self.assertEqual('1.1', ds_diff.base_version)480 self.assertEqual('1.1', ds_diff.base_version)
487481
482 def test_base_source_pub_none(self):
483 # None is simply returned if there is no base version.
484 ds_diff = self.factory.makeDistroSeriesDifference()
485
486 self.assertIs(None, ds_diff.base_version)
487 self.assertIs(None, ds_diff.base_source_pub)
488
489 def test_base_source_pub(self):
490 # The publishing in the derived series with base_version is returned.
491 ds_diff = self.factory.makeDistroSeriesDifference(versions={
492 'derived': '1.2',
493 'parent': '1.3',
494 'base': '1.0',
495 })
496
497 base_pub = ds_diff.base_source_pub
498 self.assertEqual('1.0', base_pub.source_package_version)
499 self.assertEqual(ds_diff.derived_series, base_pub.distroseries)
500
501 def test_requestPackageDiffs(self):
502 # IPackageDiffs are created for the corresponding versions.
503 ds_diff = self.factory.makeDistroSeriesDifference(versions={
504 'derived': '1.2',
505 'parent': '1.3',
506 'base': '1.0',
507 })
508
509 with person_logged_in(ds_diff.owner):
510 ds_diff.requestPackageDiffs(ds_diff.owner)
511
512 self.assertEqual(
513 '1.2', ds_diff.package_diff.to_source.version)
514 self.assertEqual(
515 '1.3', ds_diff.parent_package_diff.to_source.version)
516 self.assertEqual(
517 '1.0', ds_diff.package_diff.from_source.version)
518 self.assertEqual(
519 '1.0', ds_diff.parent_package_diff.from_source.version)
520
521 def test_package_diff_urls_none(self):
522 # URLs to the package diffs are only present when the diffs
523 # have been generated.
524 ds_diff = self.factory.makeDistroSeriesDifference()
525
526 self.assertEqual(None, ds_diff.package_diff_url)
527 self.assertEqual(None, ds_diff.parent_package_diff_url)
528
529
530class DistroSeriesDifferenceLibrarianTestCase(TestCaseWithFactory):
531
532 layer = LaunchpadFunctionalLayer
533
534 def test_package_diff_urls(self):
535 # Only completed package diffs have urls.
536 ds_diff = self.factory.makeDistroSeriesDifference()
537 naked_dsdiff = removeSecurityProxy(ds_diff)
538 naked_dsdiff.package_diff = self.factory.makePackageDiff(
539 status=PackageDiffStatus.PENDING)
540 naked_dsdiff.parent_package_diff = self.factory.makePackageDiff()
541
542 self.assertEqual(None, ds_diff.package_diff_url)
543 self.assertTrue(ds_diff.parent_package_diff_url.startswith(
544 'http://localhost:58000/'))
545
488546
489class DistroSeriesDifferenceSourceTestCase(TestCaseWithFactory):547class DistroSeriesDifferenceSourceTestCase(TestCaseWithFactory):
490548
491549
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-10-29 19:31:40 +0000
+++ lib/lp/testing/factory.py 2010-10-29 19:31:42 +0000
@@ -1946,6 +1946,15 @@
1946 if versions is None:1946 if versions is None:
1947 versions = {}1947 versions = {}
19481948
1949 base_version = versions.get('base')
1950 if base_version is not None:
1951 for series in [derived_series, derived_series.parent_series]:
1952 self.makeSourcePackagePublishingHistory(
1953 distroseries=series,
1954 version=base_version,
1955 sourcepackagename=source_package_name,
1956 status=PackagePublishingStatus.SUPERSEDED)
1957
1949 if difference_type is not (1958 if difference_type is not (
1950 DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES):1959 DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES):
19511960

Subscribers

People subscribed via source and target branches

to status/vote changes: