Merge lp:~jml/launchpad/branch-sample-data-doctests into lp:launchpad
- branch-sample-data-doctests
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Approved by: | Robert Collins |
Approved revision: | no longer in the source branch. |
Merge reported by: | Jonathan Lange |
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/branch-sample-data-doctests |
Merge into: | lp:launchpad |
Diff against target: |
2687 lines (+1841/-381) (has conflicts) 27 files modified
lib/canonical/buildd/debian/changelog (+9/-0) lib/lp/app/browser/tests/test_launchpadroot.py (+3/-0) lib/lp/archivepublisher/tests/test_dominator.py (+216/-0) lib/lp/bugs/browser/tests/test_bugsupervisor.py (+7/-0) lib/lp/bugs/browser/tests/test_bugtarget_configure.py (+55/-24) lib/lp/bugs/doc/bug-branch.txt (+0/-187) lib/lp/bugs/doc/bug.txt (+0/-33) lib/lp/bugs/model/bugbranch.py (+0/-1) lib/lp/bugs/tests/test_bugbranch.py (+230/-0) lib/lp/buildmaster/tests/test_manager.py (+3/-0) lib/lp/code/browser/sourcepackagerecipe.py (+42/-1) lib/lp/code/browser/sourcepackagerecipebuild.py (+173/-0) lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py (+191/-0) lib/lp/code/configure.zcml (+109/-0) lib/lp/soyuz/tests/test_archive.py (+281/-135) lib/lp/soyuz/tests/test_publishing.py (+61/-0) lib/lp/testing/factory.py (+17/-0) lib/lp/testing/tests/test_factory.py (+16/-0) lib/lp/translations/interfaces/potemplate.py (+10/-0) lib/lp/translations/model/potemplate.py (+88/-0) lib/lp/translations/tests/test_translationtemplatescollection.py (+210/-0) lib/lp/translations/utilities/gettext_mo_exporter.py (+41/-0) lib/lp/translations/utilities/gettext_po_exporter.py (+55/-0) lib/lp/translations/utilities/tests/test_export_file_storage.py (+9/-0) lib/lp/translations/utilities/translation_export.py (+5/-0) setup.py (+5/-0) versions.cfg (+5/-0) Text conflict in lib/canonical/buildd/debian/changelog Text conflict in lib/lp/app/browser/tests/test_launchpadroot.py Text conflict in lib/lp/archivepublisher/tests/test_dominator.py Text conflict in lib/lp/bugs/browser/tests/test_bugsupervisor.py Text conflict in lib/lp/bugs/browser/tests/test_bugtarget_configure.py Text conflict in lib/lp/buildmaster/tests/test_manager.py Text conflict in lib/lp/code/browser/sourcepackagerecipe.py Text conflict in lib/lp/code/browser/sourcepackagerecipebuild.py Text conflict in lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py Text conflict in lib/lp/code/configure.zcml Text conflict in lib/lp/soyuz/tests/test_archive.py Text conflict in lib/lp/soyuz/tests/test_publishing.py Text conflict in lib/lp/testing/factory.py Text conflict in lib/lp/testing/tests/test_factory.py Text conflict in lib/lp/translations/interfaces/potemplate.py Text conflict in lib/lp/translations/model/potemplate.py Text conflict in lib/lp/translations/tests/test_translationtemplatescollection.py Text conflict in lib/lp/translations/utilities/gettext_mo_exporter.py Text conflict in lib/lp/translations/utilities/gettext_po_exporter.py Text conflict in lib/lp/translations/utilities/tests/test_export_file_storage.py Text conflict in lib/lp/translations/utilities/translation_export.py Text conflict in setup.py Text conflict in versions.cfg |
To merge this branch: | bzr merge lp:~jml/launchpad/branch-sample-data-doctests |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Approve | ||
Review via email: mp+30426@code.launchpad.net |
Commit message
Make doc/branch.txt smaller and not use sampledata.
Description of the change
As part of an eventually-ending quest to get rid of sampledata, this branch fixes doc/branch.txt to not need any.
Specifically, it takes a bunch of crap from doc/branch.txt and moves it to unit tests. It also tweaks some of the existing doctest to use sampledata.
In one case, I removed a small piece of crappy functionality: the count of the number of branches with linked bugs that is displayed on the code.launchpad.net homepage.
The branch also fixes a minor bug where merge_control_
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'configs/development/launchpad-lazr.conf' |
2 | === modified file 'lib/canonical/buildd/debian/changelog' |
3 | --- lib/canonical/buildd/debian/changelog 2010-07-23 20:23:04 +0000 |
4 | +++ lib/canonical/buildd/debian/changelog 2010-07-29 19:06:17 +0000 |
5 | @@ -1,3 +1,4 @@ |
6 | +<<<<<<< TREE |
7 | launchpad-buildd (67) hardy-cat; urgency=low |
8 | |
9 | * Force aptitude installation for recipe builds on maverick |
10 | @@ -10,6 +11,14 @@ |
11 | |
12 | -- LaMont Jones <lamont@canonical.com> Mon, 19 Jul 2010 12:13:31 -0600 |
13 | |
14 | +======= |
15 | +launchpad-buildd (66) hardy-cat; urgency=low |
16 | + |
17 | + * handle [linux-any] build-dependencies. LP#604981 |
18 | + |
19 | + -- LaMont Jones <lamont@canonical.com> Mon, 19 Jul 2010 12:13:31 -0600 |
20 | + |
21 | +>>>>>>> MERGE-SOURCE |
22 | launchpad-buildd (65) hardy-cat; urgency=low |
23 | |
24 | * Drop preinst check, since human time does not scale across a large |
25 | |
26 | === modified file 'lib/canonical/config/schema-lazr.conf' |
27 | === modified file 'lib/canonical/configure.zcml' |
28 | === modified file 'lib/canonical/launchpad/browser/launchpad.py' |
29 | === modified file 'lib/canonical/launchpad/icing/style-3-0.css.in' |
30 | === modified file 'lib/canonical/launchpad/security.py' |
31 | === modified file 'lib/canonical/launchpad/webapp/interfaces.py' |
32 | === modified file 'lib/canonical/launchpad/webapp/publication.py' |
33 | === modified file 'lib/canonical/launchpad/webapp/servers.py' |
34 | === modified file 'lib/canonical/launchpad/webapp/tests/test_servers.py' |
35 | === modified file 'lib/lp/app/browser/tests/test_launchpadroot.py' |
36 | --- lib/lp/app/browser/tests/test_launchpadroot.py 2010-07-21 18:51:37 +0000 |
37 | +++ lib/lp/app/browser/tests/test_launchpadroot.py 2010-07-29 19:06:17 +0000 |
38 | @@ -5,7 +5,10 @@ |
39 | |
40 | __metaclass__ = type |
41 | |
42 | +<<<<<<< TREE |
43 | |
44 | +======= |
45 | +>>>>>>> MERGE-SOURCE |
46 | from BeautifulSoup import BeautifulSoup, SoupStrainer |
47 | |
48 | from zope.component import getUtility |
49 | |
50 | === modified file 'lib/lp/app/javascript/comment.js' |
51 | === modified file 'lib/lp/app/templates/base-layout-macros.pt' |
52 | === modified file 'lib/lp/app/tests/test_help.py' |
53 | === modified file 'lib/lp/archivepublisher/tests/test_dominator.py' |
54 | --- lib/lp/archivepublisher/tests/test_dominator.py 2010-07-22 11:59:40 +0000 |
55 | +++ lib/lp/archivepublisher/tests/test_dominator.py 2010-07-29 19:06:17 +0000 |
56 | @@ -6,6 +6,12 @@ |
57 | __metaclass__ = type |
58 | |
59 | import datetime |
60 | +<<<<<<< TREE |
61 | +======= |
62 | +import pytz |
63 | + |
64 | +from zope.component import getUtility |
65 | +>>>>>>> MERGE-SOURCE |
66 | |
67 | from lp.archivepublisher.domination import Dominator |
68 | from lp.archivepublisher.publishing import Publisher |
69 | @@ -61,11 +67,17 @@ |
70 | flush_database_updates() |
71 | |
72 | # The dominant version remains correctly published. |
73 | +<<<<<<< TREE |
74 | self.checkPublication(dominant, PackagePublishingStatus.PUBLISHED) |
75 | +======= |
76 | + dominant = self.checkSourcePublication( |
77 | + dominant_source, PackagePublishingStatus.PUBLISHED) |
78 | +>>>>>>> MERGE-SOURCE |
79 | self.assertTrue(dominant.supersededby is None) |
80 | self.assertTrue(dominant.datesuperseded is None) |
81 | |
82 | # The dominated version is correctly dominated. |
83 | +<<<<<<< TREE |
84 | self.checkPublication(dominated, PackagePublishingStatus.SUPERSEDED) |
85 | self.assertEqual(dominated.supersededby, supersededby) |
86 | self.checkPastDate(dominated.datesuperseded) |
87 | @@ -97,6 +109,210 @@ |
88 | dominator.judgeAndDominate( |
89 | foo_10_source.distroseries, foo_10_source.pocket, self.config) |
90 | |
91 | +======= |
92 | + dominated = self.checkSourcePublication( |
93 | + dominated_source, PackagePublishingStatus.SUPERSEDED) |
94 | + self.assertEqual( |
95 | + dominated.supersededby, dominant.sourcepackagerelease) |
96 | + self.checkPastDate(dominated.datesuperseded) |
97 | + |
98 | + def testEmptySourceDomination(self): |
99 | + """Source domination asserts for not empty input list.""" |
100 | + dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
101 | + source_input = {'foo': []} |
102 | + self.assertRaises( |
103 | + AssertionError, dominator._dominateSource, source_input) |
104 | + |
105 | + def testBinariesDomination(self): |
106 | + """Test overall binary domination procedure.""" |
107 | + dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
108 | + |
109 | + [dominant_source, dominant, dominated_source, |
110 | + dominated] = self.createSimpleDominationContext() |
111 | + |
112 | + # See comment about domination input format and ordering above. |
113 | + binary_input = {'foo-bin': [dominant, dominated]} |
114 | + |
115 | + dominator._dominateBinaries(binary_input) |
116 | + flush_database_updates() |
117 | + |
118 | + # Dominant version remains correctly published. |
119 | + dominant = self.checkBinaryPublication( |
120 | + dominant, PackagePublishingStatus.PUBLISHED) |
121 | + self.assertTrue(dominant.supersededby is None) |
122 | + self.assertTrue(dominant.datesuperseded is None) |
123 | + |
124 | + # Dominated version is correctly dominated. |
125 | + dominated = self.checkBinaryPublication( |
126 | + dominated, PackagePublishingStatus.SUPERSEDED) |
127 | + self.assertEqual( |
128 | + dominated.supersededby, dominant.binarypackagerelease.build) |
129 | + self.checkPastDate(dominated.datesuperseded) |
130 | + |
131 | + def testEmptyBinaryDomination(self): |
132 | + """Binaries domination asserts not empty input list.""" |
133 | + dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
134 | + binary_input = {'foo-bin': []} |
135 | + self.assertRaises( |
136 | + AssertionError, dominator._dominateBinaries, binary_input) |
137 | + |
138 | + def testBinaryDomination(self): |
139 | + """Test binary domination unit procedure.""" |
140 | + dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
141 | + |
142 | + [dominant_source, dominant, dominated_source, |
143 | + dominated] = self.createSimpleDominationContext() |
144 | + |
145 | + dominator._dominateBinary(dominated, dominant) |
146 | + flush_database_updates() |
147 | + |
148 | + dominated = self.checkBinaryPublication( |
149 | + dominated, PackagePublishingStatus.SUPERSEDED) |
150 | + self.assertEqual( |
151 | + dominated.supersededby, dominant.binarypackagerelease.build) |
152 | + self.checkPastDate(dominated.datesuperseded) |
153 | + |
154 | + def testBinaryDominationAssertsPendingOrPublished(self): |
155 | + """Test binary domination asserts coherent dominated status. |
156 | + |
157 | + Normally _dominateBinary only accepts domination candidates in |
158 | + PUBLISHED or PENDING status, a exception is opened for architecture |
159 | + independent binaries because during the iteration they might have |
160 | + been already SUPERSEDED with its first publication, when it happens |
161 | + the candidate is skipped, i.e. it's not dominated again. |
162 | + |
163 | + (remembering the architecture independent binaries get superseded |
164 | + atomically) |
165 | + """ |
166 | + dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
167 | + |
168 | + [dominant_source, dominant, dominated_source, |
169 | + dominated] = self.createSimpleDominationContext() |
170 | + |
171 | + # Let's modify the domination candidate, so it will look wrong to |
172 | + # _dominateBinary which will raise because it's a architecture |
173 | + # specific binary publication not in PENDING or PUBLISHED state. |
174 | + dominated.status = PackagePublishingStatus.SUPERSEDED |
175 | + manual_domination_date = datetime.datetime( |
176 | + 2006, 12, 25, tzinfo=pytz.timezone("UTC")) |
177 | + dominated.datesuperseded = manual_domination_date |
178 | + flush_database_updates() |
179 | + |
180 | + # An error like that in production clearly indicates that something |
181 | + # is wrong in the Dominator look-up methods. |
182 | + self.assertRaises( |
183 | + AssertionError, dominator._dominateBinary, dominated, dominant) |
184 | + |
185 | + # The refused publishing record remains the same. |
186 | + dominated = self.checkBinaryPublication( |
187 | + dominated, PackagePublishingStatus.SUPERSEDED) |
188 | + self.assertEqual(dominated.datesuperseded, manual_domination_date) |
189 | + |
190 | + # Let's make it a architecture independent binary, so the domination |
191 | + # can be executed, but the record will be skipped. |
192 | + dominated.binarypackagerelease.architecturespecific = False |
193 | + flush_database_updates() |
194 | + |
195 | + dominator._dominateBinary(dominated, dominant) |
196 | + flush_database_updates() |
197 | + dominated = self.checkBinaryPublication( |
198 | + dominated, PackagePublishingStatus.SUPERSEDED) |
199 | + self.assertEqual(dominated.datesuperseded, manual_domination_date) |
200 | + |
201 | + def testOtherBinaryPublications(self): |
202 | + """Check the basis of architecture independent binary domination. |
203 | + |
204 | + We use _getOtherBinaryPublications to identify other publications of |
205 | + the same binarypackagerelease in other architectures (architecture |
206 | + independent binaries), they will be dominated during a single step. |
207 | + |
208 | + See overall details in `testDominationOfOldArchIndepBinaries`. |
209 | + """ |
210 | + dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
211 | + |
212 | + # Create architecture independent publications for foo-bin_1.0 |
213 | + # in i386 & hppa. |
214 | + pub_source_archindep = self.getPubSource( |
215 | + version='1.0', status=PackagePublishingStatus.PUBLISHED, |
216 | + architecturehintlist='all') |
217 | + pub_binaries_archindep = self.getPubBinaries( |
218 | + pub_source=pub_source_archindep, |
219 | + status=PackagePublishingStatus.PUBLISHED) |
220 | + [hppa_pub, i386_pub] = pub_binaries_archindep |
221 | + |
222 | + # We will also copy the binary publications to a PPA archive |
223 | + # to check if the lookup is indeed restricted to the dominated |
224 | + # archive. See bug #237845 for further information. |
225 | + cprov = getUtility(IPersonSet).getByName('cprov') |
226 | + i386_pub.copyTo( |
227 | + self.breezy_autotest, |
228 | + PackagePublishingPocket.RELEASE, |
229 | + cprov.archive) |
230 | + cprov_foo_binaries = cprov.archive.getAllPublishedBinaries(name='foo') |
231 | + self.assertEqual(cprov_foo_binaries.count(), 2) |
232 | + |
233 | + # Manually supersede the hppa binary. |
234 | + hppa_pub.status = PackagePublishingStatus.SUPERSEDED |
235 | + flush_database_updates() |
236 | + |
237 | + # Check if we can reach the i386 publication using |
238 | + # _getOtherBinaryPublications over the hppa binary. |
239 | + [found] = list(dominator._getOtherBinaryPublications(hppa_pub)) |
240 | + self.assertEqual(i386_pub, found) |
241 | + |
242 | + # Create architecture specific publications for foo-bin_1.1 in |
243 | + # i386 & hppa. |
244 | + pub_source_archdep = self.getPubSource( |
245 | + version='1.1', status=PackagePublishingStatus.PUBLISHED, |
246 | + architecturehintlist='any') |
247 | + pub_binaries_archdep = self.getPubBinaries( |
248 | + pub_source=pub_source_archdep) |
249 | + [hppa_pub, i386_pub] = pub_binaries_archdep |
250 | + |
251 | + # Manually supersede the hppa publication. |
252 | + hppa_pub.status = PackagePublishingStatus.SUPERSEDED |
253 | + flush_database_updates() |
254 | + |
255 | + # Check if there is no other publication of the hppa binary package |
256 | + # release. |
257 | + self.assertEqual( |
258 | + dominator._getOtherBinaryPublications(hppa_pub).count(), |
259 | + 0) |
260 | + |
261 | + def testDominationOfOldArchIndepBinaries(self): |
262 | + """Check domination of architecture independent binaries. |
263 | + |
264 | + When a architecture independent binary is dominated it should also |
265 | + 'carry' the same publications in other architectures independently |
266 | + of whether or not the new binary was successfully built to a specific |
267 | + architecture. |
268 | + |
269 | + See bug #48760 for further information about this aspect. |
270 | + """ |
271 | + publisher = Publisher( |
272 | + self.logger, self.config, self.disk_pool, |
273 | + self.ubuntutest.main_archive) |
274 | + |
275 | + # Create published archindep context. |
276 | + pub_source_archindep = self.getPubSource( |
277 | + version='1.0', status=PackagePublishingStatus.PUBLISHED, |
278 | + architecturehintlist='all') |
279 | + pub_binaries_archindep = self.getPubBinaries( |
280 | + pub_source=pub_source_archindep, |
281 | + status=PackagePublishingStatus.PUBLISHED) |
282 | + |
283 | + # Emulated new publication of a archdep binary only on i386. |
284 | + pub_source_archdep = self.getPubSource( |
285 | + version='1.1', architecturehintlist='i386') |
286 | + pub_binaries_archdep = self.getPubBinaries( |
287 | + pub_source=pub_source_archdep) |
288 | + |
289 | + publisher.A_publish(False) |
290 | + publisher.B_dominate(False) |
291 | + |
292 | + # The latest architecture specific source and binary pair is |
293 | + # PUBLISHED. |
294 | +>>>>>>> MERGE-SOURCE |
295 | self.checkPublications( |
296 | [foo_12_source] + foo_12_binaries, |
297 | PackagePublishingStatus.PUBLISHED) |
298 | |
299 | === modified file 'lib/lp/archivepublisher/tests/test_publisher.py' |
300 | === modified file 'lib/lp/archiveuploader/tests/test_uploadprocessor.py' |
301 | === modified file 'lib/lp/bugs/browser/tests/test_bugsupervisor.py' |
302 | --- lib/lp/bugs/browser/tests/test_bugsupervisor.py 2010-07-18 00:55:24 +0000 |
303 | +++ lib/lp/bugs/browser/tests/test_bugsupervisor.py 2010-07-29 19:06:17 +0000 |
304 | @@ -171,3 +171,10 @@ |
305 | self.product, name='+bugsupervisor', form=form) |
306 | self.assertEqual([], view.errors) |
307 | self.assertEqual(private_team, self.product.bug_supervisor) |
308 | +<<<<<<< TREE |
309 | +======= |
310 | + |
311 | + |
312 | +def test_suite(): |
313 | + return unittest.TestLoader().loadTestsFromName(__name__) |
314 | +>>>>>>> MERGE-SOURCE |
315 | |
316 | === modified file 'lib/lp/bugs/browser/tests/test_bugtarget_configure.py' |
317 | --- lib/lp/bugs/browser/tests/test_bugtarget_configure.py 2010-07-21 07:50:49 +0000 |
318 | +++ lib/lp/bugs/browser/tests/test_bugtarget_configure.py 2010-07-29 19:06:17 +0000 |
319 | @@ -120,27 +120,58 @@ |
320 | self.assertEqual([], view.errors) |
321 | self.assertFalse(self.product.enable_bug_expiration) |
322 | |
323 | - def test_bug_role_non_admin_can_edit(self): |
324 | - # Verify that a member of an owning team who is not an admin of |
325 | - # the bug supervisor team or security_contact team can change bug |
326 | - # reporting guidelines. |
327 | - owning_team = self.factory.makeTeam(owner=self.owner) |
328 | - bug_team = self.factory.makeTeam(owner=self.owner) |
329 | - weak_owner = self.factory.makePerson() |
330 | - login_person(self.owner) |
331 | - owning_team.addMember(weak_owner, self.owner) |
332 | - bug_team.addMember(weak_owner, self.owner) |
333 | - self.product.owner = owning_team |
334 | - self.product.setBugSupervisor(bug_team, self.owner) |
335 | - self.product.security_contact = bug_team |
336 | - login_person(weak_owner) |
337 | - form = self._makeForm() |
338 | - # Only the bug_reporting_guidelines are different. |
339 | - form['field.bug_supervisor'] = bug_team.name |
340 | - form['field.security_contact'] = bug_team.name |
341 | - form['field.bug_reporting_guidelines'] = 'new guidelines' |
342 | - view = create_initialized_view( |
343 | - self.product, name='+configure-bugtracker', form=form) |
344 | - self.assertEqual([], view.errors) |
345 | - self.assertEqual( |
346 | - 'new guidelines', self.product.bug_reporting_guidelines) |
347 | +<<<<<<< TREE |
348 | + def test_bug_role_non_admin_can_edit(self): |
349 | + # Verify that a member of an owning team who is not an admin of |
350 | + # the bug supervisor team or security_contact team can change bug |
351 | + # reporting guidelines. |
352 | + owning_team = self.factory.makeTeam(owner=self.owner) |
353 | + bug_team = self.factory.makeTeam(owner=self.owner) |
354 | + weak_owner = self.factory.makePerson() |
355 | + login_person(self.owner) |
356 | + owning_team.addMember(weak_owner, self.owner) |
357 | + bug_team.addMember(weak_owner, self.owner) |
358 | + self.product.owner = owning_team |
359 | + self.product.setBugSupervisor(bug_team, self.owner) |
360 | + self.product.security_contact = bug_team |
361 | + login_person(weak_owner) |
362 | + form = self._makeForm() |
363 | + # Only the bug_reporting_guidelines are different. |
364 | + form['field.bug_supervisor'] = bug_team.name |
365 | + form['field.security_contact'] = bug_team.name |
366 | + form['field.bug_reporting_guidelines'] = 'new guidelines' |
367 | + view = create_initialized_view( |
368 | + self.product, name='+configure-bugtracker', form=form) |
369 | + self.assertEqual([], view.errors) |
370 | + self.assertEqual( |
371 | + 'new guidelines', self.product.bug_reporting_guidelines) |
372 | +======= |
373 | + def test_bug_role_non_admin_can_edit(self): |
374 | + # Verify that a member of an owning team who is not an admin of |
375 | + # the bug supervisor team or security_contact team can change bug |
376 | + # reporting guidelines. |
377 | + owning_team = self.factory.makeTeam(owner=self.owner) |
378 | + bug_team = self.factory.makeTeam(owner=self.owner) |
379 | + weak_owner = self.factory.makePerson() |
380 | + login_person(self.owner) |
381 | + owning_team.addMember(weak_owner, self.owner) |
382 | + bug_team.addMember(weak_owner, self.owner) |
383 | + self.product.owner = owning_team |
384 | + self.product.setBugSupervisor(bug_team, self.owner) |
385 | + self.product.security_contact = bug_team |
386 | + login_person(weak_owner) |
387 | + form = self._makeForm() |
388 | + # Only the bug_reporting_guidelines are different. |
389 | + form['field.bug_supervisor'] = bug_team.name |
390 | + form['field.security_contact'] = bug_team.name |
391 | + form['field.bug_reporting_guidelines'] = 'new guidelines' |
392 | + view = create_initialized_view( |
393 | + self.product, name='+configure-bugtracker', form=form) |
394 | + self.assertEqual([], view.errors) |
395 | + self.assertEqual( |
396 | + 'new guidelines', self.product.bug_reporting_guidelines) |
397 | + |
398 | + |
399 | +def test_suite(): |
400 | + return unittest.TestLoader().loadTestsFromName(__name__) |
401 | +>>>>>>> MERGE-SOURCE |
402 | |
403 | === modified file 'lib/lp/bugs/configure.zcml' |
404 | === removed file 'lib/lp/bugs/doc/bug-branch.txt' |
405 | --- lib/lp/bugs/doc/bug-branch.txt 2010-03-29 12:57:20 +0000 |
406 | +++ lib/lp/bugs/doc/bug-branch.txt 1970-01-01 00:00:00 +0000 |
407 | @@ -1,187 +0,0 @@ |
408 | -= Bugs and Branches = |
409 | - |
410 | -Branches can be linked to Bugs, to track work in progress on branches, |
411 | -and when fixes are committed. |
412 | - |
413 | - >>> from canonical.launchpad.webapp.testing import verifyObject |
414 | - >>> from canonical.launchpad.interfaces import IBugBranch, IBugBranchSet |
415 | - >>> from canonical.launchpad.database import BugBranch, BugBranchSet |
416 | - >>> verifyObject(IBugBranch, BugBranch.get(1)) |
417 | - True |
418 | - >>> verifyObject(IBugBranchSet, BugBranchSet()) |
419 | - True |
420 | - |
421 | - |
422 | -== BugBranch == |
423 | - |
424 | -BugBranch links a bug and a branch. |
425 | - |
426 | - >>> from canonical.launchpad.interfaces import ( |
427 | - ... IBugSet, IPersonSet) |
428 | - >>> from lp.code.interfaces.branchlookup import IBranchLookup |
429 | - >>> from canonical.database.sqlbase import flush_database_updates |
430 | - |
431 | - >>> login("no-priv@canonical.com") |
432 | - |
433 | - >>> bugset = getUtility(IBugSet) |
434 | - >>> bug = bugset.get(1) |
435 | - >>> branch = getUtility(IBranchLookup).get(10) |
436 | - |
437 | -Adding a branch to a bug returns an IBugBranch. |
438 | - |
439 | - >>> user = getUtility(IPersonSet).getByEmail("no-priv@canonical.com") |
440 | - >>> bug_branch = bug.linkBranch(branch, user) |
441 | - |
442 | - >>> flush_database_updates() |
443 | - |
444 | -The bug and branch fields of the returned bug_branch should reflect our |
445 | -sample data. |
446 | - |
447 | - >>> bug_branch.bug.id |
448 | - 1 |
449 | - >>> bug_branch.branch.id |
450 | - 10 |
451 | - >>> print bug_branch.registrant.displayname |
452 | - No Privileges Person |
453 | - |
454 | - >>> [bug_branch.branch.name for bug_branch in |
455 | - ... bug.linked_branches] |
456 | - [u'release-0.9.2'] |
457 | - |
458 | -Trying to add a branch that is already linked to a bug will simply |
459 | -return the existing BugBranch. |
460 | - |
461 | - >>> bug_branch_two = bug.linkBranch(branch, user) |
462 | - >>> bug_branch_two == bug_branch |
463 | - True |
464 | - |
465 | -You can check if a branch is linked to a bug: |
466 | - |
467 | - >>> bug.hasBranch(branch) |
468 | - True |
469 | - |
470 | - >>> bug_two = bugset.get(2) |
471 | - >>> bug_two.hasBranch(branch) |
472 | - False |
473 | - >>> branch = getUtility(IBranchLookup).get(1) |
474 | - >>> bug_branch_three = bug.linkBranch(branch, user) |
475 | - |
476 | -If we make the bug private, no-priv won't be allowed to edit either |
477 | -the bug or the BugBranch. |
478 | - |
479 | - >>> login('foo.bar@canonical.com') |
480 | - >>> bug.setPrivate(True, getUtility(ILaunchBag).user) |
481 | - True |
482 | - >>> login('no-priv@canonical.com') |
483 | - |
484 | - >>> bug.description = 'Yet another description.' |
485 | - Traceback (most recent call last): |
486 | - ... |
487 | - Unauthorized:... |
488 | - |
489 | - >>> bug.unlinkBranch(branch, getUtility(ILaunchBag).user) |
490 | - Traceback (most recent call last): |
491 | - ... |
492 | - Unauthorized:... |
493 | - |
494 | - >>> another_branch = factory.makeAnyBranch() |
495 | - >>> bug.linkBranch(another_branch, getUtility(ILaunchBag).user) |
496 | - Traceback (most recent call last): |
497 | - ... |
498 | - Unauthorized:... |
499 | - |
500 | - >>> login('foo.bar@canonical.com') |
501 | - >>> bug.setPrivate(False, getUtility(ILaunchBag).user) |
502 | - True |
503 | - |
504 | -Likewise, anonymous users cannot link or unlink branches. |
505 | - |
506 | - >>> login(ANONYMOUS) |
507 | - |
508 | - >>> bug.unlinkBranch(branch, getUtility(ILaunchBag).user) |
509 | - Traceback (most recent call last): |
510 | - ... |
511 | - Unauthorized:... |
512 | - |
513 | - >>> bug.linkBranch(another_branch, getUtility(ILaunchBag).user) |
514 | - Traceback (most recent call last): |
515 | - ... |
516 | - Unauthorized:... |
517 | - |
518 | - >>> login('no-priv@canonical.com') |
519 | - |
520 | -You can unlink a branch from a bug using unlinkBranch(). |
521 | - |
522 | - >>> bug.hasBranch(branch) |
523 | - True |
524 | - >>> bug.unlinkBranch(branch, getUtility(ILaunchBag).user) |
525 | - >>> bug.hasBranch(branch) |
526 | - False |
527 | - |
528 | -Calling unlinkBranch() once again is a noop. |
529 | - |
530 | - >>> bug.unlinkBranch(branch, getUtility(ILaunchBag).user) |
531 | - |
532 | - >>> bug_branch = bug.linkBranch(branch, user) |
533 | - |
534 | - |
535 | -== Bugs Related to Branches == |
536 | - |
537 | -The bugs related to a branch are accessible via the linked_bugs |
538 | -property. |
539 | - |
540 | - >>> sorted([bug.id for bug in branch.linked_bugs]) |
541 | - [1, 4, 5] |
542 | - |
543 | - |
544 | -== Getting bug branches associated with multiple branches == |
545 | - |
546 | -Sometimes we want to get the associated bug branch links for a set of branches. |
547 | -The getBugBranchesForBranches method can do this. |
548 | - |
549 | -Firstly we need to get the branches. |
550 | - |
551 | - >>> from canonical.launchpad.interfaces import IBugBranchSet |
552 | - >>> branches = [getUtility(IBranchLookup).getByUniqueName( |
553 | - ... '~carlos/iso-codes/0.35'), |
554 | - ... getUtility(IBranchLookup).getByUniqueName( |
555 | - ... '~mark/firefox/release-0.9.2')] |
556 | - |
557 | -The bug branches returned are only those where the user can see the |
558 | -bugs that are associated. This way if there are bugs associated with |
559 | -a branch that the user cannot see, then they are not shown. |
560 | - |
561 | - >>> from canonical.launchpad.interfaces import IPersonSet |
562 | - >>> user = getUtility(IPersonSet).getByEmail('test@canonical.com') |
563 | - >>> bugbranches = getUtility(IBugBranchSet).getBugBranchesForBranches( |
564 | - ... branches, user) |
565 | - >>> for bugbranch in sorted(bugbranches, |
566 | - ... key=lambda b: (b.branch.id, b.bug.id)): |
567 | - ... print "%s <-> %s" % ( |
568 | - ... bugbranch.branch.unique_name, bugbranch.bug.id) |
569 | - ~mark/firefox/release-0.9.2 <-> 1 |
570 | - ~mark/firefox/release-0.9.2 <-> 4 |
571 | - |
572 | - |
573 | -== Getting bug branches associated with multiple bugs == |
574 | - |
575 | -Sometimes we want to get the associated bug branch links for a set of bugs. |
576 | -The getBugBranchesForBugs method can do this. |
577 | - |
578 | -Firstly we need to get the branches. We'll look up the relationships |
579 | -for bugs 1, 2, 3 and 4. |
580 | - |
581 | - >>> bugtasks = list(getUtility(IBugSet).get(1).bugtasks) |
582 | - >>> bugtasks.extend(getUtility(IBugSet).get(2).bugtasks) |
583 | - >>> bugtasks.extend(getUtility(IBugSet).get(3).bugtasks) |
584 | - >>> bugtasks.extend(getUtility(IBugSet).get(4).bugtasks) |
585 | - >>> bugbranches2 = getUtility(IBugBranchSet).getBugBranchesForBugTasks( |
586 | - ... bugtasks) |
587 | - >>> for bugbranch2 in sorted(bugbranches2, |
588 | - ... key=lambda b: (b.branch.id, b.bug.id)): |
589 | - ... print "%s <-> %s" % (bugbranch2.branch.unique_name, bugbranch2.bug.id) |
590 | - ~name12/firefox/main <-> 1 |
591 | - ~name12/firefox/main <-> 4 |
592 | - ~mark/firefox/release-0.9.2 <-> 1 |
593 | - ~mark/firefox/release-0.9.2 <-> 4 |
594 | - |
595 | |
596 | === modified file 'lib/lp/bugs/doc/bug.txt' |
597 | --- lib/lp/bugs/doc/bug.txt 2010-07-22 12:17:41 +0000 |
598 | +++ lib/lp/bugs/doc/bug.txt 2010-07-29 19:06:17 +0000 |
599 | @@ -834,39 +834,6 @@ |
600 | >>> firefox_bug.date_last_updated > current_date_last_updated |
601 | True |
602 | |
603 | -Adding a branch. |
604 | - |
605 | - >>> from lp.code.interfaces.branchlookup import IBranchLookup |
606 | - >>> firefox_bug.linked_branches.count() |
607 | - 0 |
608 | - |
609 | - >>> branch_one = getUtility(IBranchLookup).get(1) |
610 | - >>> current_date_last_updated = firefox_bug.date_last_updated |
611 | - |
612 | - >>> bug_branch = firefox_bug.linkBranch(branch_one, foobar) |
613 | - |
614 | - >>> firefox_bug.linked_branches.count() |
615 | - 1 |
616 | - >>> firefox_bug.date_last_updated > current_date_last_updated |
617 | - True |
618 | - |
619 | -Editing a branch. |
620 | - |
621 | - >>> from lp.bugs.interfaces.bugbranch import IBugBranch |
622 | - |
623 | - >>> branch_before_modification = Snapshot( |
624 | - ... bug_branch, providing=IBugBranch) |
625 | - |
626 | - >>> bug_branch_changed = ObjectModifiedEvent( |
627 | - ... bug_branch, branch_before_modification, ["status"]) |
628 | - |
629 | - >>> current_date_last_updated = firefox_bug.date_last_updated |
630 | - |
631 | - >>> notify(bug_branch_changed) |
632 | - |
633 | - >>> firefox_bug.date_last_updated > current_date_last_updated |
634 | - True |
635 | - |
636 | Linking to a CVE. |
637 | |
638 | >>> from lp.bugs.interfaces.cve import ICveSet |
639 | |
640 | === modified file 'lib/lp/bugs/mail/bugnotificationrecipients.py' |
641 | === modified file 'lib/lp/bugs/model/bugbranch.py' |
642 | --- lib/lp/bugs/model/bugbranch.py 2009-07-17 00:26:05 +0000 |
643 | +++ lib/lp/bugs/model/bugbranch.py 2010-07-29 19:06:17 +0000 |
644 | @@ -18,7 +18,6 @@ |
645 | from canonical.database.constants import UTC_NOW |
646 | from canonical.database.datetimecol import UtcDateTimeCol |
647 | from canonical.database.sqlbase import SQLBase, sqlvalues |
648 | -from canonical.database.enumcol import EnumCol |
649 | |
650 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
651 | from lp.bugs.interfaces.bugbranch import IBugBranch, IBugBranchSet |
652 | |
653 | === modified file 'lib/lp/bugs/templates/bugtask-index.pt' |
654 | === added file 'lib/lp/bugs/tests/test_bugbranch.py' |
655 | --- lib/lp/bugs/tests/test_bugbranch.py 1970-01-01 00:00:00 +0000 |
656 | +++ lib/lp/bugs/tests/test_bugbranch.py 2010-07-29 19:06:17 +0000 |
657 | @@ -0,0 +1,230 @@ |
658 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
659 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
660 | + |
661 | +from __future__ import with_statement |
662 | + |
663 | +"""Tests for bug-branch linking from the bugs side.""" |
664 | + |
665 | +__metaclass__ = type |
666 | + |
667 | +from zope.component import getUtility |
668 | +from zope.event import notify |
669 | +from zope.security.interfaces import Unauthorized |
670 | + |
671 | +from canonical.testing import DatabaseFunctionalLayer |
672 | +from lazr.lifecycle.event import ObjectModifiedEvent |
673 | +from lazr.lifecycle.snapshot import Snapshot |
674 | +from lp.bugs.model.bugbranch import BugBranch, BugBranchSet |
675 | +from lp.bugs.interfaces.bugbranch import IBugBranch, IBugBranchSet |
676 | +from lp.testing import ( |
677 | + anonymous_logged_in, |
678 | + celebrity_logged_in, |
679 | + TestCaseWithFactory, |
680 | + ) |
681 | + |
682 | + |
683 | +class TestBugBranchSet(TestCaseWithFactory): |
684 | + |
685 | + layer = DatabaseFunctionalLayer |
686 | + |
687 | + def test_bugbranchset_provides_IBugBranchSet(self): |
688 | + # BugBranchSet objects provide IBugBranchSet. |
689 | + self.assertProvides(BugBranchSet(), IBugBranchSet) |
690 | + |
691 | + def test_getBugBranchesForBranches_no_branches(self): |
692 | + bug_branches = getUtility(IBugBranchSet) |
693 | + links = bug_branches.getBugBranchesForBranches( |
694 | + [], self.factory.makePerson()) |
695 | + self.assertEqual([], list(links)) |
696 | + |
697 | + def test_getBugBranchesForBranches(self): |
698 | + # IBugBranchSet.getBugBranchesForBranches returns all of the BugBranch |
699 | + # objects associated with the given branches. |
700 | + branch_1 = self.factory.makeBranch() |
701 | + branch_2 = self.factory.makeBranch() |
702 | + bug_a = self.factory.makeBug() |
703 | + bug_b = self.factory.makeBug() |
704 | + self.factory.loginAsAnyone() |
705 | + link_1 = bug_a.linkBranch(branch_1, self.factory.makePerson()) |
706 | + link_2 = bug_a.linkBranch(branch_2, self.factory.makePerson()) |
707 | + link_3 = bug_b.linkBranch(branch_2, self.factory.makePerson()) |
708 | + self.assertEqual( |
709 | + set([link_1, link_2, link_3]), |
710 | + set(getUtility(IBugBranchSet).getBugBranchesForBranches( |
711 | + [branch_1, branch_2], self.factory.makePerson()))) |
712 | + |
713 | + def test_getBugBranchesForBranches_respects_bug_privacy(self): |
714 | + # IBugBranchSet.getBugBranchesForBranches returns only the BugBranch |
715 | + # objects that are visible by the user who is asking for them. |
716 | + branch = self.factory.makeBranch() |
717 | + user = self.factory.makePerson() |
718 | + public_bug = self.factory.makeBug() |
719 | + private_visible_bug = self.factory.makeBug(private=True) |
720 | + private_invisible_bug = self.factory.makeBug(private=True) |
721 | + with celebrity_logged_in('admin'): |
722 | + public_bug.linkBranch(branch, user) |
723 | + private_visible_bug.subscribe(user, user) |
724 | + private_visible_bug.linkBranch(branch, user) |
725 | + private_invisible_bug.linkBranch(branch, user) |
726 | + bug_branches = getUtility(IBugBranchSet).getBugBranchesForBranches( |
727 | + [branch], user) |
728 | + self.assertEqual( |
729 | + set([public_bug, private_visible_bug]), |
730 | + set([link.bug for link in bug_branches])) |
731 | + |
732 | + def test_getBugBranchesForBugTasks(self): |
733 | + # IBugBranchSet.getBugBranchesForBugTasks returns all of the BugBranch |
734 | + # objects associated with the given bug tasks. |
735 | + bug_a = self.factory.makeBug() |
736 | + bug_b = self.factory.makeBug() |
737 | + bugtasks = bug_a.bugtasks + bug_b.bugtasks |
738 | + branch = self.factory.makeBranch() |
739 | + self.factory.loginAsAnyone() |
740 | + link_1 = bug_a.linkBranch(branch, self.factory.makePerson()) |
741 | + link_2 = bug_b.linkBranch(branch, self.factory.makePerson()) |
742 | + found_links = getUtility(IBugBranchSet).getBugBranchesForBugTasks( |
743 | + bugtasks) |
744 | + self.assertEqual(set([link_1, link_2]), set(found_links)) |
745 | + |
746 | + |
747 | +class TestBugBranch(TestCaseWithFactory): |
748 | + |
749 | + layer = DatabaseFunctionalLayer |
750 | + |
751 | + def setUp(self): |
752 | + super(TestBugBranch, self).setUp() |
753 | + # Bug branch linking is generally available to any logged in user. |
754 | + self.factory.loginAsAnyone() |
755 | + |
756 | + def test_bugbranch_provides_IBugBranch(self): |
757 | + # BugBranch objects provide IBugBranch. |
758 | + bug_branch = BugBranch( |
759 | + branch=self.factory.makeBranch(), bug=self.factory.makeBug(), |
760 | + registrant=self.factory.makePerson()) |
761 | + self.assertProvides(bug_branch, IBugBranch) |
762 | + |
763 | + def test_linkBranch_returns_IBugBranch(self): |
764 | + # Bug.linkBranch returns an IBugBranch linking the bug to the branch. |
765 | + bug = self.factory.makeBug() |
766 | + branch = self.factory.makeBranch() |
767 | + registrant = self.factory.makePerson() |
768 | + bug_branch = bug.linkBranch(branch, registrant) |
769 | + self.assertEqual(branch, bug_branch.branch) |
770 | + self.assertEqual(bug, bug_branch.bug) |
771 | + self.assertEqual(registrant, bug_branch.registrant) |
772 | + |
773 | + def test_bug_start_with_no_linked_branches(self): |
774 | + # Bugs have a linked_branches attribute which is initially an empty |
775 | + # collection. |
776 | + bug = self.factory.makeBug() |
777 | + self.assertEqual([], list(bug.linked_branches)) |
778 | + |
779 | + def test_linkBranch_adds_to_linked_branches(self): |
780 | + # Bug.linkBranch populates the Bug.linked_branches with the created |
781 | + # BugBranch object. |
782 | + bug = self.factory.makeBug() |
783 | + branch = self.factory.makeBranch() |
784 | + bug_branch = bug.linkBranch(branch, self.factory.makePerson()) |
785 | + self.assertEqual([bug_branch], list(bug.linked_branches)) |
786 | + |
787 | + def test_linking_branch_twice_returns_same_IBugBranch(self): |
788 | + # Calling Bug.linkBranch twice with the same parameters returns the |
789 | + # same object. |
790 | + bug = self.factory.makeBug() |
791 | + branch = self.factory.makeBranch() |
792 | + bug_branch = bug.linkBranch(branch, self.factory.makePerson()) |
793 | + bug_branch_2 = bug.linkBranch(branch, self.factory.makePerson()) |
794 | + self.assertEqual(bug_branch, bug_branch_2) |
795 | + |
796 | + def test_linking_branch_twice_different_registrants(self): |
797 | + # Calling Bug.linkBranch twice with the branch but different |
798 | + # registrants returns the existing bug branch object rather than |
799 | + # creating a new one. |
800 | + bug = self.factory.makeBug() |
801 | + branch = self.factory.makeBranch() |
802 | + bug_branch = bug.linkBranch(branch, self.factory.makePerson()) |
803 | + bug_branch_2 = bug.linkBranch(branch, self.factory.makePerson()) |
804 | + self.assertEqual(bug_branch, bug_branch_2) |
805 | + |
806 | + def test_bug_has_no_branches(self): |
807 | + # Bug.hasBranch returns False for any branch that it is not linked to. |
808 | + bug = self.factory.makeBug() |
809 | + self.assertFalse(bug.hasBranch(self.factory.makeBranch())) |
810 | + |
811 | + def test_bug_has_branch(self): |
812 | + # Bug.hasBranch returns False for any branch that it is linked to. |
813 | + bug = self.factory.makeBug() |
814 | + branch = self.factory.makeBranch() |
815 | + bug.linkBranch(branch, self.factory.makePerson()) |
816 | + self.assertTrue(bug.hasBranch(branch)) |
817 | + |
818 | + def test_unlink_branch(self): |
819 | + # Bug.unlinkBranch removes the bug<->branch link. |
820 | + bug = self.factory.makeBug() |
821 | + branch = self.factory.makeBranch() |
822 | + bug.linkBranch(branch, self.factory.makePerson()) |
823 | + bug.unlinkBranch(branch, self.factory.makePerson()) |
824 | + self.assertEqual([], list(bug.linked_branches)) |
825 | + self.assertFalse(bug.hasBranch(branch)) |
826 | + |
827 | + def test_unlink_not_linked_branch(self): |
828 | + # When unlinkBranch is called with a branch that isn't already linked, |
829 | + # nothing discernable happens. |
830 | + bug = self.factory.makeBug() |
831 | + branch = self.factory.makeBranch() |
832 | + bug.unlinkBranch(branch, self.factory.makePerson()) |
833 | + self.assertEqual([], list(bug.linked_branches)) |
834 | + self.assertFalse(bug.hasBranch(branch)) |
835 | + |
836 | + def test_the_unwashed_cannot_link_branch_to_private_bug(self): |
837 | + # Those who cannot see a bug are forbidden to link a branch to it. |
838 | + bug = self.factory.makeBug(private=True) |
839 | + self.assertRaises(Unauthorized, getattr, bug, 'linkBranch') |
840 | + |
841 | + def test_the_unwashed_cannot_unlink_branch_from_private_bug(self): |
842 | + # Those who cannot see a bug are forbidden to unlink branches from it. |
843 | + bug = self.factory.makeBug(private=True) |
844 | + self.assertRaises(Unauthorized, getattr, bug, 'unlinkBranch') |
845 | + |
846 | + def test_anonymous_users_cannot_link_branches(self): |
847 | + # Anonymous users cannot link branches to bugs, even public bugs. |
848 | + bug = self.factory.makeBug() |
849 | + with anonymous_logged_in(): |
850 | + self.assertRaises(Unauthorized, getattr, bug, 'linkBranch') |
851 | + |
852 | + def test_anonymous_users_cannot_unlink_branches(self): |
853 | + # Anonymous users cannot unlink branches from bugs, even public bugs. |
854 | + bug = self.factory.makeBug() |
855 | + with anonymous_logged_in(): |
856 | + self.assertRaises(Unauthorized, getattr, bug, 'unlinkBranch') |
857 | + |
858 | + def test_adding_branch_changes_date_last_updated(self): |
859 | + # Adding a branch to a bug changes IBug.date_last_updated. |
860 | + bug = self.factory.makeBug() |
861 | + last_updated = bug.date_last_updated |
862 | + branch = self.factory.makeBranch() |
863 | + self.factory.loginAsAnyone() |
864 | + bug.linkBranch(branch, self.factory.makePerson()) |
865 | + self.assertTrue(bug.date_last_updated > last_updated) |
866 | + |
867 | + def test_editing_branch_changes_date_last_updated(self): |
868 | + # Editing a branch linked to a bug changes IBug.date_last_updated. |
869 | + bug = self.factory.makeBug() |
870 | + branch = self.factory.makeBranch() |
871 | + registrant = self.factory.makePerson() |
872 | + self.factory.loginAsAnyone() |
873 | + branch_link = bug.linkBranch(branch, registrant) |
874 | + last_updated = bug.date_last_updated |
875 | + # Rather than modifying the bugbranch link directly, we emit an |
876 | + # ObjectModifiedEvent, which is triggered whenever the object is |
877 | + # edited. |
878 | + |
879 | + # XXX: jml has no idea why we do this. Accessing any attribute of the |
880 | + # returned BugBranch appears to be forbidden, and there's no evidence |
881 | + # that the object is even editable at all. |
882 | + before_modification = Snapshot(branch_link, providing=IBugBranch) |
883 | + # XXX: WTF? IBugBranch doesn't even have a status attribute? jml. |
884 | + event = ObjectModifiedEvent( |
885 | + branch_link, before_modification, ['status']) |
886 | + notify(event) |
887 | + self.assertTrue(bug.date_last_updated > last_updated) |
888 | |
889 | === modified file 'lib/lp/buildmaster/tests/test_manager.py' |
890 | --- lib/lp/buildmaster/tests/test_manager.py 2010-07-28 09:56:24 +0000 |
891 | +++ lib/lp/buildmaster/tests/test_manager.py 2010-07-29 19:06:17 +0000 |
892 | @@ -915,6 +915,7 @@ |
893 | # The `buildd-manager.tac` starts and stops correctly. |
894 | BuilddManagerTestSetup().setUp() |
895 | BuilddManagerTestSetup().tearDown() |
896 | +<<<<<<< TREE |
897 | |
898 | def testBuilddManagerLogging(self): |
899 | # The twistd process logs as execpected. |
900 | @@ -954,3 +955,5 @@ |
901 | self.assertFalse( |
902 | os.access(rotated_logfilepath, os.F_OK), |
903 | "Twistd's log file was rotated by twistd.") |
904 | +======= |
905 | +>>>>>>> MERGE-SOURCE |
906 | |
907 | === modified file 'lib/lp/code/browser/configure.zcml' |
908 | === modified file 'lib/lp/code/browser/sourcepackagerecipe.py' |
909 | --- lib/lp/code/browser/sourcepackagerecipe.py 2010-07-23 03:53:46 +0000 |
910 | +++ lib/lp/code/browser/sourcepackagerecipe.py 2010-07-29 19:06:17 +0000 |
911 | @@ -28,6 +28,10 @@ |
912 | |
913 | from canonical.database.constants import UTC_NOW |
914 | from canonical.launchpad.browser.launchpad import Hierarchy |
915 | +<<<<<<< TREE |
916 | +======= |
917 | +from canonical.launchpad.interfaces import ILaunchBag |
918 | +>>>>>>> MERGE-SOURCE |
919 | from canonical.launchpad.webapp import ( |
920 | action, canonical_url, ContextMenu, custom_widget, |
921 | enabled_with_permission, LaunchpadEditFormView, LaunchpadFormView, |
922 | @@ -40,7 +44,15 @@ |
923 | from lp.code.interfaces.sourcepackagerecipe import ( |
924 | ISourcePackageRecipe, ISourcePackageRecipeSource, MINIMAL_RECIPE_TEXT) |
925 | from lp.code.interfaces.sourcepackagerecipebuild import ( |
926 | - ISourcePackageRecipeBuildSource) |
927 | +<<<<<<< TREE |
928 | + ISourcePackageRecipeBuildSource) |
929 | +======= |
930 | + ISourcePackageRecipeBuildSource) |
931 | +from lp.soyuz.browser.archive import make_archive_vocabulary |
932 | +from lp.soyuz.interfaces.archive import ( |
933 | + IArchiveSet) |
934 | +from lp.registry.interfaces.distroseries import IDistroSeriesSet |
935 | +>>>>>>> MERGE-SOURCE |
936 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
937 | |
938 | RECIPE_BETA_MESSAGE = structured( |
939 | @@ -170,6 +182,31 @@ |
940 | return builds |
941 | |
942 | |
943 | +<<<<<<< TREE |
944 | +======= |
945 | +def buildable_distroseries_vocabulary(context): |
946 | + """Return a vocabulary of buildable distroseries.""" |
947 | + ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user) |
948 | + supported_distros = [ppa.distribution for ppa in ppas] |
949 | + dsset = getUtility(IDistroSeriesSet).search() |
950 | + terms = sorted_dotted_numbers( |
951 | + [SimpleTerm(distro, distro.id, distro.displayname) |
952 | + for distro in dsset if ( |
953 | + distro.active and distro.distribution in supported_distros)], |
954 | + key=lambda term: term.value.version) |
955 | + terms.reverse() |
956 | + return SimpleVocabulary(terms) |
957 | + |
958 | + |
959 | +def target_ppas_vocabulary(context): |
960 | + """Return a vocabulary of ppas that the current user can target.""" |
961 | + ppas = getUtility(IArchiveSet).getPPAsForUser(getUtility(ILaunchBag).user) |
962 | + return make_archive_vocabulary( |
963 | + ppa for ppa in ppas |
964 | + if check_permission('launchpad.Append', ppa)) |
965 | + |
966 | + |
967 | +>>>>>>> MERGE-SOURCE |
968 | class SourcePackageRecipeRequestBuildsView(LaunchpadFormView): |
969 | """A view for requesting builds of a SourcePackageRecipe.""" |
970 | |
971 | @@ -232,6 +269,10 @@ |
972 | self.next_url = self.cancel_url |
973 | |
974 | |
975 | +<<<<<<< TREE |
976 | +======= |
977 | + |
978 | +>>>>>>> MERGE-SOURCE |
979 | class ISourcePackageAddEditSchema(Interface): |
980 | """Schema for adding or editing a recipe.""" |
981 | |
982 | |
983 | === modified file 'lib/lp/code/browser/sourcepackagerecipebuild.py' |
984 | --- lib/lp/code/browser/sourcepackagerecipebuild.py 2010-07-27 14:25:54 +0000 |
985 | +++ lib/lp/code/browser/sourcepackagerecipebuild.py 2010-07-29 19:06:17 +0000 |
986 | @@ -1,3 +1,4 @@ |
987 | +<<<<<<< TREE |
988 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
989 | # GNU Affero General Public License version 3 (see the file LICENSE). |
990 | |
991 | @@ -165,3 +166,175 @@ |
992 | @property |
993 | def initial_values(self): |
994 | return {'score': str(self.context.buildqueue_record.lastscore)} |
995 | +======= |
996 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
997 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
998 | + |
999 | +"""SourcePackageRecipeBuild views.""" |
1000 | + |
1001 | +__metaclass__ = type |
1002 | + |
1003 | +__all__ = [ |
1004 | + 'SourcePackageRecipeBuildContextMenu', |
1005 | + 'SourcePackageRecipeBuildNavigation', |
1006 | + 'SourcePackageRecipeBuildView', |
1007 | + 'SourcePackageRecipeBuildCancelView', |
1008 | + 'SourcePackageRecipeBuildRescoreView', |
1009 | + ] |
1010 | + |
1011 | +from zope.interface import Interface |
1012 | +from zope.schema import Text |
1013 | + |
1014 | +from canonical.launchpad.browser.librarian import FileNavigationMixin |
1015 | +from canonical.launchpad.webapp import ( |
1016 | + action, canonical_url, ContextMenu, enabled_with_permission, |
1017 | + LaunchpadView, LaunchpadFormView, Link, Navigation) |
1018 | + |
1019 | +from lp.buildmaster.interfaces.buildbase import BuildStatus |
1020 | +from lp.code.interfaces.sourcepackagerecipebuild import ( |
1021 | + ISourcePackageRecipeBuild) |
1022 | +from lp.services.job.interfaces.job import JobStatus |
1023 | + |
1024 | + |
1025 | +class SourcePackageRecipeBuildNavigation(Navigation, FileNavigationMixin): |
1026 | + |
1027 | + usedfor = ISourcePackageRecipeBuild |
1028 | + |
1029 | + |
1030 | +class SourcePackageRecipeBuildContextMenu(ContextMenu): |
1031 | + """Navigation menu for sourcepackagerecipe build.""" |
1032 | + |
1033 | + usedfor = ISourcePackageRecipeBuild |
1034 | + |
1035 | + facet = 'branches' |
1036 | + |
1037 | + links = ('cancel', 'rescore') |
1038 | + |
1039 | + @enabled_with_permission('launchpad.Edit') |
1040 | + def cancel(self): |
1041 | + if self.context.buildqueue_record is None: |
1042 | + enabled = False |
1043 | + else: |
1044 | + enabled = True |
1045 | + return Link('+cancel', 'Cancel build', icon='remove', enabled=enabled) |
1046 | + |
1047 | + @enabled_with_permission('launchpad.Edit') |
1048 | + def rescore(self): |
1049 | + if self.context.buildqueue_record is None: |
1050 | + enabled = False |
1051 | + else: |
1052 | + enabled = True |
1053 | + return Link('+rescore', 'Rescore build', icon='edit', enabled=enabled) |
1054 | + |
1055 | + |
1056 | +class SourcePackageRecipeBuildView(LaunchpadView): |
1057 | + """Default view of a SourcePackageRecipeBuild.""" |
1058 | + |
1059 | + @property |
1060 | + def status(self): |
1061 | + """A human-friendly status string.""" |
1062 | + if (self.context.buildstate == BuildStatus.NEEDSBUILD |
1063 | + and self.eta is None): |
1064 | + return 'No suitable builders' |
1065 | + return { |
1066 | + BuildStatus.NEEDSBUILD: 'Pending build', |
1067 | + BuildStatus.FULLYBUILT: 'Successful build', |
1068 | + BuildStatus.MANUALDEPWAIT: ( |
1069 | + 'Could not build because of missing dependencies'), |
1070 | + BuildStatus.CHROOTWAIT: ( |
1071 | + 'Could not build because of chroot problem'), |
1072 | + BuildStatus.SUPERSEDED: ( |
1073 | + 'Could not build because source package was superseded'), |
1074 | + BuildStatus.FAILEDTOUPLOAD: 'Could not be uploaded correctly', |
1075 | + }.get(self.context.buildstate, self.context.buildstate.title) |
1076 | + |
1077 | + @property |
1078 | + def eta(self): |
1079 | + """The datetime when the build job is estimated to complete. |
1080 | + |
1081 | + This is the BuildQueue.estimated_duration plus the |
1082 | + Job.date_started or BuildQueue.getEstimatedJobStartTime. |
1083 | + """ |
1084 | + if self.context.buildqueue_record is None: |
1085 | + return None |
1086 | + queue_record = self.context.buildqueue_record |
1087 | + if queue_record.job.status == JobStatus.WAITING: |
1088 | + start_time = queue_record.getEstimatedJobStartTime() |
1089 | + if start_time is None: |
1090 | + return None |
1091 | + else: |
1092 | + start_time = queue_record.job.date_started |
1093 | + duration = queue_record.estimated_duration |
1094 | + return start_time + duration |
1095 | + |
1096 | + @property |
1097 | + def date(self): |
1098 | + """The date when the build completed or is estimated to complete.""" |
1099 | + if self.estimate: |
1100 | + return self.eta |
1101 | + return self.context.datebuilt |
1102 | + |
1103 | + @property |
1104 | + def estimate(self): |
1105 | + """If true, the date value is an estimate.""" |
1106 | + if self.context.datebuilt is not None: |
1107 | + return False |
1108 | + return self.eta is not None |
1109 | + |
1110 | + def binary_builds(self): |
1111 | + return list(self.context.binary_builds) |
1112 | + |
1113 | + |
1114 | +class SourcePackageRecipeBuildCancelView(LaunchpadFormView): |
1115 | + """View for cancelling a build.""" |
1116 | + |
1117 | + class schema(Interface): |
1118 | + """Schema for cancelling a build.""" |
1119 | + |
1120 | + page_title = label = "Cancel build" |
1121 | + |
1122 | + @property |
1123 | + def cancel_url(self): |
1124 | + return canonical_url(self.context) |
1125 | + next_url = cancel_url |
1126 | + |
1127 | + @action('Cancel build', name='cancel') |
1128 | + def request_action(self, action, data): |
1129 | + """Cancel the build.""" |
1130 | + self.context.cancelBuild() |
1131 | + |
1132 | + |
1133 | +class SourcePackageRecipeBuildRescoreView(LaunchpadFormView): |
1134 | + """View for rescoring a build.""" |
1135 | + |
1136 | + class schema(Interface): |
1137 | + """Schema for deleting a build.""" |
1138 | + score = Text( |
1139 | + title=u'Score', required=True, |
1140 | + description=u'The score of the recipe.') |
1141 | + |
1142 | + page_title = label = "Rescore build" |
1143 | + |
1144 | + @property |
1145 | + def cancel_url(self): |
1146 | + return canonical_url(self.context) |
1147 | + next_url = cancel_url |
1148 | + |
1149 | + def validate(self, data): |
1150 | + try: |
1151 | + score = int(data['score']) |
1152 | + except ValueError: |
1153 | + self.setFieldError( |
1154 | + 'score', |
1155 | + 'You have specified an invalid value for score. ' |
1156 | + 'Please specify an integer') |
1157 | + |
1158 | + @action('Rescore build', name='rescore') |
1159 | + def request_action(self, action, data): |
1160 | + """Rescore the build.""" |
1161 | + self.context.buildqueue_record.lastscore = int(data['score']) |
1162 | + |
1163 | + @property |
1164 | + def initial_values(self): |
1165 | + return {'score': str(self.context.buildqueue_record.lastscore)} |
1166 | +>>>>>>> MERGE-SOURCE |
1167 | |
1168 | === modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py' |
1169 | === modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py' |
1170 | --- lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-27 14:25:54 +0000 |
1171 | +++ lib/lp/code/browser/tests/test_sourcepackagerecipebuild.py 2010-07-29 19:06:17 +0000 |
1172 | @@ -1,3 +1,4 @@ |
1173 | +<<<<<<< TREE |
1174 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
1175 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1176 | # pylint: disable-msg=F0401,E1002 |
1177 | @@ -190,3 +191,193 @@ |
1178 | self.assertRaises( |
1179 | LinkNotFoundError, |
1180 | browser.getLink, 'Rescore build') |
1181 | +======= |
1182 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
1183 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1184 | +# pylint: disable-msg=F0401,E1002 |
1185 | + |
1186 | +"""Tests for the source package recipe view classes and templates.""" |
1187 | + |
1188 | +__metaclass__ = type |
1189 | + |
1190 | +from mechanize import LinkNotFoundError |
1191 | +import transaction |
1192 | +from zope.component import getUtility |
1193 | + |
1194 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
1195 | +from canonical.launchpad.testing.pages import ( |
1196 | + extract_text, find_tags_by_class) |
1197 | +from canonical.launchpad.webapp import canonical_url |
1198 | +from canonical.testing import DatabaseFunctionalLayer |
1199 | +from lp.buildmaster.interfaces.buildbase import BuildStatus |
1200 | +from lp.soyuz.model.processor import ProcessorFamily |
1201 | +from lp.testing import ANONYMOUS, BrowserTestCase, login, logout |
1202 | + |
1203 | + |
1204 | +class TestSourcePackageRecipeBuild(BrowserTestCase): |
1205 | + """Create some sample data for recipe tests.""" |
1206 | + |
1207 | + layer = DatabaseFunctionalLayer |
1208 | + |
1209 | + def setUp(self): |
1210 | + """Provide useful defaults.""" |
1211 | + super(TestSourcePackageRecipeBuild, self).setUp() |
1212 | + self.chef = self.factory.makePerson( |
1213 | + displayname='Master Chef', name='chef', password='test') |
1214 | + self.user = self.chef |
1215 | + self.ppa = self.factory.makeArchive( |
1216 | + displayname='Secret PPA', owner=self.chef, name='ppa') |
1217 | + self.squirrel = self.factory.makeDistroSeries( |
1218 | + displayname='Secret Squirrel', name='secret', version='100.04', |
1219 | + distribution=self.ppa.distribution) |
1220 | + self.squirrel.nominatedarchindep = self.squirrel.newArch( |
1221 | + 'i386', ProcessorFamily.get(1), False, self.chef, |
1222 | + supports_virtualized=True) |
1223 | + |
1224 | + def makeRecipeBuild(self): |
1225 | + """Create and return a specific recipe.""" |
1226 | + chocolate = self.factory.makeProduct(name='chocolate') |
1227 | + cake_branch = self.factory.makeProductBranch( |
1228 | + owner=self.chef, name='cake', product=chocolate) |
1229 | + recipe = self.factory.makeSourcePackageRecipe( |
1230 | + owner=self.chef, distroseries=self.squirrel, name=u'cake_recipe', |
1231 | + description=u'This recipe builds a foo for disto bar, with my' |
1232 | + ' Secret Squirrel changes.', branches=[cake_branch], |
1233 | + daily_build_archive=self.ppa) |
1234 | + build = self.factory.makeSourcePackageRecipeBuild( |
1235 | + recipe=recipe) |
1236 | + return build |
1237 | + |
1238 | + def test_cancel_build(self): |
1239 | + """An admin can cancel a build.""" |
1240 | + experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner |
1241 | + queue = self.factory.makeSourcePackageRecipeBuildJob() |
1242 | + build = queue.specific_job.build |
1243 | + transaction.commit() |
1244 | + build_url = canonical_url(build) |
1245 | + logout() |
1246 | + |
1247 | + browser = self.getUserBrowser(build_url, user=experts) |
1248 | + browser.getLink('Cancel build').click() |
1249 | + |
1250 | + self.assertEqual( |
1251 | + browser.getLink('Cancel').url, |
1252 | + build_url) |
1253 | + |
1254 | + browser.getControl('Cancel build').click() |
1255 | + |
1256 | + self.assertEqual( |
1257 | + browser.url, |
1258 | + build_url) |
1259 | + |
1260 | + login(ANONYMOUS) |
1261 | + self.assertEqual( |
1262 | + BuildStatus.SUPERSEDED, |
1263 | + build.status) |
1264 | + |
1265 | + def test_cancel_build_not_admin(self): |
1266 | + """No one but admins can cancel a build.""" |
1267 | + queue = self.factory.makeSourcePackageRecipeBuildJob() |
1268 | + build = queue.specific_job.build |
1269 | + transaction.commit() |
1270 | + build_url = canonical_url(build) |
1271 | + logout() |
1272 | + |
1273 | + browser = self.getUserBrowser(build_url, user=self.chef) |
1274 | + self.assertRaises( |
1275 | + LinkNotFoundError, |
1276 | + browser.getLink, 'Cancel build') |
1277 | + |
1278 | + def test_cancel_build_wrong_state(self): |
1279 | + """If the build isn't queued, you can't cancel it.""" |
1280 | + experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner |
1281 | + build = self.makeRecipeBuild() |
1282 | + transaction.commit() |
1283 | + build_url = canonical_url(build) |
1284 | + logout() |
1285 | + |
1286 | + browser = self.getUserBrowser(build_url, user=experts) |
1287 | + self.assertRaises( |
1288 | + LinkNotFoundError, |
1289 | + browser.getLink, 'Cancel build') |
1290 | + |
1291 | + def test_rescore_build(self): |
1292 | + """An admin can rescore a build.""" |
1293 | + experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner |
1294 | + queue = self.factory.makeSourcePackageRecipeBuildJob() |
1295 | + build = queue.specific_job.build |
1296 | + transaction.commit() |
1297 | + build_url = canonical_url(build) |
1298 | + logout() |
1299 | + |
1300 | + browser = self.getUserBrowser(build_url, user=experts) |
1301 | + browser.getLink('Rescore build').click() |
1302 | + |
1303 | + self.assertEqual( |
1304 | + browser.getLink('Cancel').url, |
1305 | + build_url) |
1306 | + |
1307 | + browser.getControl('Score').value = '1024' |
1308 | + |
1309 | + browser.getControl('Rescore build').click() |
1310 | + |
1311 | + self.assertEqual( |
1312 | + browser.url, |
1313 | + build_url) |
1314 | + |
1315 | + login(ANONYMOUS) |
1316 | + self.assertEqual( |
1317 | + build.buildqueue_record.lastscore, |
1318 | + 1024) |
1319 | + |
1320 | + def test_rescore_build_invalid_score(self): |
1321 | + """Build scores can only take numbers.""" |
1322 | + experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner |
1323 | + queue = self.factory.makeSourcePackageRecipeBuildJob() |
1324 | + build = queue.specific_job.build |
1325 | + transaction.commit() |
1326 | + build_url = canonical_url(build) |
1327 | + logout() |
1328 | + |
1329 | + browser = self.getUserBrowser(build_url, user=experts) |
1330 | + browser.getLink('Rescore build').click() |
1331 | + |
1332 | + self.assertEqual( |
1333 | + browser.getLink('Cancel').url, |
1334 | + build_url) |
1335 | + |
1336 | + browser.getControl('Score').value = 'tentwentyfour' |
1337 | + |
1338 | + browser.getControl('Rescore build').click() |
1339 | + |
1340 | + self.assertEqual( |
1341 | + extract_text(find_tags_by_class(browser.contents, 'message')[1]), |
1342 | + 'You have specified an invalid value for score. ' |
1343 | + 'Please specify an integer') |
1344 | + |
1345 | + def test_rescore_build_not_admin(self): |
1346 | + """No one but admins can rescore a build.""" |
1347 | + queue = self.factory.makeSourcePackageRecipeBuildJob() |
1348 | + build = queue.specific_job.build |
1349 | + transaction.commit() |
1350 | + build_url = canonical_url(build) |
1351 | + logout() |
1352 | + |
1353 | + browser = self.getUserBrowser(build_url, user=self.chef) |
1354 | + self.assertRaises( |
1355 | + LinkNotFoundError, |
1356 | + browser.getLink, 'Rescore build') |
1357 | + |
1358 | + def test_rescore_build_wrong_state(self): |
1359 | + """If the build isn't queued, you can't rescore it.""" |
1360 | + experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner |
1361 | + build = self.makeRecipeBuild() |
1362 | + transaction.commit() |
1363 | + build_url = canonical_url(build) |
1364 | + logout() |
1365 | + |
1366 | + browser = self.getUserBrowser(build_url, user=experts) |
1367 | + self.assertRaises( |
1368 | + LinkNotFoundError, |
1369 | + browser.getLink, 'Rescore build') |
1370 | +>>>>>>> MERGE-SOURCE |
1371 | |
1372 | === modified file 'lib/lp/code/configure.zcml' |
1373 | --- lib/lp/code/configure.zcml 2010-07-25 12:55:56 +0000 |
1374 | +++ lib/lp/code/configure.zcml 2010-07-29 19:06:17 +0000 |
1375 | @@ -439,12 +439,121 @@ |
1376 | <class class="lp.code.model.branch.Branch"> |
1377 | <require |
1378 | permission="launchpad.View" |
1379 | +<<<<<<< TREE |
1380 | interface="canonical.launchpad.interfaces.launchpad.IPrivacy |
1381 | lp.code.interfaces.branch.IBranchAnyone |
1382 | lp.code.interfaces.branch.IBranchEditableAttributes |
1383 | lp.code.interfaces.branch.IBranchPublic |
1384 | lp.code.interfaces.branch.IBranchView |
1385 | "/> |
1386 | +======= |
1387 | + attributes="id |
1388 | + branch_type |
1389 | + name |
1390 | + url |
1391 | + composePublicURL |
1392 | + whiteboard |
1393 | + target |
1394 | + mirror_status_message |
1395 | + private |
1396 | + registrant |
1397 | + owner |
1398 | + description |
1399 | + author |
1400 | + reviewer |
1401 | + code_reviewer |
1402 | + isPersonTrustedReviewer |
1403 | + product |
1404 | + unique_name |
1405 | + displayname |
1406 | + sort_key |
1407 | + lifecycle_status |
1408 | + last_mirrored |
1409 | + last_mirrored_id |
1410 | + last_mirror_attempt |
1411 | + mirror_failures |
1412 | + merge_control_status |
1413 | + pull_disabled |
1414 | + next_mirror_time |
1415 | + last_scanned |
1416 | + last_scanned_id |
1417 | + revision_count |
1418 | + bug_branches |
1419 | + linked_bugs |
1420 | + getLinkedBugsAndTasks |
1421 | + linkBug |
1422 | + unlinkBug |
1423 | + spec_links |
1424 | + linkSpecification |
1425 | + unlinkSpecification |
1426 | + revision_history |
1427 | + subscriptions |
1428 | + subscribers |
1429 | + date_created |
1430 | + date_last_modified |
1431 | + latest_revisions |
1432 | + landing_targets |
1433 | + landing_candidates |
1434 | + dependent_branches |
1435 | + _createMergeProposal |
1436 | + addLandingTarget |
1437 | + scheduleDiffUpdates |
1438 | + getMergeQueue |
1439 | + getRevisionsSince |
1440 | + code_is_browseable |
1441 | + browse_source_url |
1442 | + code_import |
1443 | + bzr_identity |
1444 | + canBeDeleted |
1445 | + deletionRequirements |
1446 | + associatedProductSeries |
1447 | + getProductSeriesPushingTranslations |
1448 | + associatedSuiteSourcePackages |
1449 | + branchIdentities |
1450 | + branchLinks |
1451 | + subscribe |
1452 | + getSubscription |
1453 | + hasSubscription |
1454 | + unsubscribe |
1455 | + getSubscriptionsByLevel |
1456 | + getBranchRevision |
1457 | + getMainlineBranchRevisions |
1458 | + getMergeProposals |
1459 | + getStackedBranches |
1460 | + createBranchRevision |
1461 | + getTipRevision |
1462 | + updateScannedDetails |
1463 | + getNotificationRecipients |
1464 | + getScannerData |
1465 | + getPullURL |
1466 | + getInternalBzrUrl |
1467 | + getBzrBranch |
1468 | + requestMirror |
1469 | + startMirroring |
1470 | + mirrorFailed |
1471 | + branch_format |
1472 | + repository_format |
1473 | + control_format |
1474 | + stacked_on |
1475 | + createBranchRevisionFromIDs |
1476 | + distroseries |
1477 | + sourcepackagename |
1478 | + addToLaunchBag |
1479 | + distribution |
1480 | + sourcepackage |
1481 | + codebrowse_url |
1482 | + merge_queue |
1483 | + namespace |
1484 | + pending_writes |
1485 | + commitsForDays |
1486 | + needs_upgrading |
1487 | + upgrade_pending |
1488 | + getUpgradeFormat |
1489 | + isBranchMergeable |
1490 | + visibleByUser |
1491 | + getRecipes |
1492 | + "/> |
1493 | +>>>>>>> MERGE-SOURCE |
1494 | <require |
1495 | permission="launchpad.Edit" |
1496 | interface="lp.code.interfaces.branch.IBranchEdit" |
1497 | |
1498 | === modified file 'lib/lp/code/interfaces/branch.py' |
1499 | === modified file 'lib/lp/code/model/branch.py' |
1500 | === modified file 'lib/lp/code/model/sourcepackagerecipe.py' |
1501 | === modified file 'lib/lp/code/model/tests/test_branch.py' |
1502 | === modified file 'lib/lp/code/model/tests/test_sourcepackagerecipebuild.py' |
1503 | === modified file 'lib/lp/code/templates/sourcepackagerecipebuild-index.pt' |
1504 | === modified file 'lib/lp/registry/model/distroseries.py' |
1505 | === modified file 'lib/lp/registry/model/person.py' |
1506 | === modified file 'lib/lp/registry/model/productseries.py' |
1507 | === modified file 'lib/lp/registry/tests/test_distroseries.py' |
1508 | === modified file 'lib/lp/soyuz/doc/archive.txt' |
1509 | === modified file 'lib/lp/soyuz/interfaces/archive.py' |
1510 | === modified file 'lib/lp/soyuz/model/archive.py' |
1511 | === modified file 'lib/lp/soyuz/model/binarypackagebuild.py' |
1512 | === modified file 'lib/lp/soyuz/model/publishing.py' |
1513 | === modified file 'lib/lp/soyuz/scripts/tests/test_gina.py' |
1514 | === modified file 'lib/lp/soyuz/scripts/tests/test_populatearchive.py' |
1515 | === modified file 'lib/lp/soyuz/tests/test_archive.py' |
1516 | --- lib/lp/soyuz/tests/test_archive.py 2010-07-21 07:45:50 +0000 |
1517 | +++ lib/lp/soyuz/tests/test_archive.py 2010-07-29 19:06:17 +0000 |
1518 | @@ -7,8 +7,12 @@ |
1519 | import unittest |
1520 | |
1521 | import pytz |
1522 | +<<<<<<< TREE |
1523 | import transaction |
1524 | |
1525 | +======= |
1526 | +import transaction |
1527 | +>>>>>>> MERGE-SOURCE |
1528 | from zope.component import getUtility |
1529 | from zope.security.interfaces import Unauthorized |
1530 | from zope.security.proxy import removeSecurityProxy |
1531 | @@ -1101,138 +1105,280 @@ |
1532 | login("commercial-member@canonical.com") |
1533 | self.setCommercial(self.archive, True) |
1534 | self.assertTrue(self.archive.commercial) |
1535 | - |
1536 | - |
1537 | -class TestFindDepCandidates(TestCaseWithFactory): |
1538 | - """Tests for Archive.findDepCandidates.""" |
1539 | - |
1540 | - layer = LaunchpadZopelessLayer |
1541 | - |
1542 | - def setUp(self): |
1543 | - super(TestFindDepCandidates, self).setUp() |
1544 | - self.archive = self.factory.makeArchive() |
1545 | - self.publisher = SoyuzTestPublisher() |
1546 | - login('admin@canonical.com') |
1547 | - self.publisher.prepareBreezyAutotest() |
1548 | - |
1549 | - def assertDep(self, arch_tag, name, expected, archive=None, |
1550 | - pocket=PackagePublishingPocket.RELEASE, component=None, |
1551 | - source_package_name='something-new'): |
1552 | - """Helper to check that findDepCandidates works. |
1553 | - |
1554 | - Searches for the given dependency name in the given architecture and |
1555 | - archive, and compares it to the given expected value. |
1556 | - The archive defaults to self.archive. |
1557 | - |
1558 | - Also commits, since findDepCandidates uses the slave store. |
1559 | - """ |
1560 | - transaction.commit() |
1561 | - |
1562 | - if component is None: |
1563 | - component = getUtility(IComponentSet)['main'] |
1564 | - if archive is None: |
1565 | - archive = self.archive |
1566 | - |
1567 | - self.assertEquals( |
1568 | - list( |
1569 | - archive.findDepCandidates( |
1570 | - self.publisher.distroseries[arch_tag], pocket, component, |
1571 | - source_package_name, name)), |
1572 | - expected) |
1573 | - |
1574 | - def test_finds_candidate_in_same_archive(self): |
1575 | - # A published candidate in the same archive should be found. |
1576 | - bins = self.publisher.getPubBinaries( |
1577 | - binaryname='foo', archive=self.archive, |
1578 | - status=PackagePublishingStatus.PUBLISHED) |
1579 | - self.assertDep('i386', 'foo', [bins[0]]) |
1580 | - self.assertDep('hppa', 'foo', [bins[1]]) |
1581 | - |
1582 | - def test_does_not_find_pending_publication(self): |
1583 | - # A pending candidate in the same archive should not be found. |
1584 | - bins = self.publisher.getPubBinaries( |
1585 | - binaryname='foo', archive=self.archive) |
1586 | - self.assertDep('i386', 'foo', []) |
1587 | - |
1588 | - def test_ppa_searches_primary_archive(self): |
1589 | - # PPA searches implicitly look in the primary archive too. |
1590 | - self.assertEquals(self.archive.purpose, ArchivePurpose.PPA) |
1591 | - self.assertDep('i386', 'foo', []) |
1592 | - |
1593 | - bins = self.publisher.getPubBinaries( |
1594 | - binaryname='foo', archive=self.archive.distribution.main_archive, |
1595 | - status=PackagePublishingStatus.PUBLISHED) |
1596 | - |
1597 | - self.assertDep('i386', 'foo', [bins[0]]) |
1598 | - |
1599 | - def test_searches_dependencies(self): |
1600 | - # Candidates from archives on which the target explicitly depends |
1601 | - # should be found. |
1602 | - bins = self.publisher.getPubBinaries( |
1603 | - binaryname='foo', archive=self.archive, |
1604 | - status=PackagePublishingStatus.PUBLISHED) |
1605 | - other_archive = self.factory.makeArchive() |
1606 | - self.assertDep('i386', 'foo', [], archive=other_archive) |
1607 | - |
1608 | - other_archive.addArchiveDependency( |
1609 | - self.archive, PackagePublishingPocket.RELEASE) |
1610 | - self.assertDep('i386', 'foo', [bins[0]], archive=other_archive) |
1611 | - |
1612 | - def test_obeys_dependency_pockets(self): |
1613 | - # Only packages published in a pocket matching the dependency should |
1614 | - # be found. |
1615 | - release_bins = self.publisher.getPubBinaries( |
1616 | - binaryname='foo-release', archive=self.archive, |
1617 | - status=PackagePublishingStatus.PUBLISHED) |
1618 | - updates_bins = self.publisher.getPubBinaries( |
1619 | - binaryname='foo-updates', archive=self.archive, |
1620 | - status=PackagePublishingStatus.PUBLISHED, |
1621 | - pocket=PackagePublishingPocket.UPDATES) |
1622 | - proposed_bins = self.publisher.getPubBinaries( |
1623 | - binaryname='foo-proposed', archive=self.archive, |
1624 | - status=PackagePublishingStatus.PUBLISHED, |
1625 | - pocket=PackagePublishingPocket.PROPOSED) |
1626 | - |
1627 | - # Temporarily turn our test PPA into a copy archive, so we can |
1628 | - # add non-RELEASE dependencies on it. |
1629 | - removeSecurityProxy(self.archive).purpose = ArchivePurpose.COPY |
1630 | - |
1631 | - other_archive = self.factory.makeArchive() |
1632 | - other_archive.addArchiveDependency( |
1633 | - self.archive, PackagePublishingPocket.UPDATES) |
1634 | - self.assertDep( |
1635 | - 'i386', 'foo-release', [release_bins[0]], archive=other_archive) |
1636 | - self.assertDep( |
1637 | - 'i386', 'foo-updates', [updates_bins[0]], archive=other_archive) |
1638 | - self.assertDep('i386', 'foo-proposed', [], archive=other_archive) |
1639 | - |
1640 | - other_archive.removeArchiveDependency(self.archive) |
1641 | - other_archive.addArchiveDependency( |
1642 | - self.archive, PackagePublishingPocket.PROPOSED) |
1643 | - self.assertDep( |
1644 | - 'i386', 'foo-proposed', [proposed_bins[0]], archive=other_archive) |
1645 | - |
1646 | - def test_obeys_dependency_components(self): |
1647 | - # Only packages published in a component matching the dependency |
1648 | - # should be found. |
1649 | - primary = self.archive.distribution.main_archive |
1650 | - main_bins = self.publisher.getPubBinaries( |
1651 | - binaryname='foo-main', archive=primary, component='main', |
1652 | - status=PackagePublishingStatus.PUBLISHED) |
1653 | - universe_bins = self.publisher.getPubBinaries( |
1654 | - binaryname='foo-universe', archive=primary, |
1655 | - component='universe', |
1656 | - status=PackagePublishingStatus.PUBLISHED) |
1657 | - |
1658 | - self.archive.addArchiveDependency( |
1659 | - primary, PackagePublishingPocket.RELEASE, |
1660 | - component=getUtility(IComponentSet)['main']) |
1661 | - self.assertDep('i386', 'foo-main', [main_bins[0]]) |
1662 | - self.assertDep('i386', 'foo-universe', []) |
1663 | - |
1664 | - self.archive.removeArchiveDependency(primary) |
1665 | - self.archive.addArchiveDependency( |
1666 | - primary, PackagePublishingPocket.RELEASE, |
1667 | - component=getUtility(IComponentSet)['universe']) |
1668 | - self.assertDep('i386', 'foo-main', [main_bins[0]]) |
1669 | - self.assertDep('i386', 'foo-universe', [universe_bins[0]]) |
1670 | +<<<<<<< TREE |
1671 | + |
1672 | + |
1673 | +class TestFindDepCandidates(TestCaseWithFactory): |
1674 | + """Tests for Archive.findDepCandidates.""" |
1675 | + |
1676 | + layer = LaunchpadZopelessLayer |
1677 | + |
1678 | + def setUp(self): |
1679 | + super(TestFindDepCandidates, self).setUp() |
1680 | + self.archive = self.factory.makeArchive() |
1681 | + self.publisher = SoyuzTestPublisher() |
1682 | + login('admin@canonical.com') |
1683 | + self.publisher.prepareBreezyAutotest() |
1684 | + |
1685 | + def assertDep(self, arch_tag, name, expected, archive=None, |
1686 | + pocket=PackagePublishingPocket.RELEASE, component=None, |
1687 | + source_package_name='something-new'): |
1688 | + """Helper to check that findDepCandidates works. |
1689 | + |
1690 | + Searches for the given dependency name in the given architecture and |
1691 | + archive, and compares it to the given expected value. |
1692 | + The archive defaults to self.archive. |
1693 | + |
1694 | + Also commits, since findDepCandidates uses the slave store. |
1695 | + """ |
1696 | + transaction.commit() |
1697 | + |
1698 | + if component is None: |
1699 | + component = getUtility(IComponentSet)['main'] |
1700 | + if archive is None: |
1701 | + archive = self.archive |
1702 | + |
1703 | + self.assertEquals( |
1704 | + list( |
1705 | + archive.findDepCandidates( |
1706 | + self.publisher.distroseries[arch_tag], pocket, component, |
1707 | + source_package_name, name)), |
1708 | + expected) |
1709 | + |
1710 | + def test_finds_candidate_in_same_archive(self): |
1711 | + # A published candidate in the same archive should be found. |
1712 | + bins = self.publisher.getPubBinaries( |
1713 | + binaryname='foo', archive=self.archive, |
1714 | + status=PackagePublishingStatus.PUBLISHED) |
1715 | + self.assertDep('i386', 'foo', [bins[0]]) |
1716 | + self.assertDep('hppa', 'foo', [bins[1]]) |
1717 | + |
1718 | + def test_does_not_find_pending_publication(self): |
1719 | + # A pending candidate in the same archive should not be found. |
1720 | + bins = self.publisher.getPubBinaries( |
1721 | + binaryname='foo', archive=self.archive) |
1722 | + self.assertDep('i386', 'foo', []) |
1723 | + |
1724 | + def test_ppa_searches_primary_archive(self): |
1725 | + # PPA searches implicitly look in the primary archive too. |
1726 | + self.assertEquals(self.archive.purpose, ArchivePurpose.PPA) |
1727 | + self.assertDep('i386', 'foo', []) |
1728 | + |
1729 | + bins = self.publisher.getPubBinaries( |
1730 | + binaryname='foo', archive=self.archive.distribution.main_archive, |
1731 | + status=PackagePublishingStatus.PUBLISHED) |
1732 | + |
1733 | + self.assertDep('i386', 'foo', [bins[0]]) |
1734 | + |
1735 | + def test_searches_dependencies(self): |
1736 | + # Candidates from archives on which the target explicitly depends |
1737 | + # should be found. |
1738 | + bins = self.publisher.getPubBinaries( |
1739 | + binaryname='foo', archive=self.archive, |
1740 | + status=PackagePublishingStatus.PUBLISHED) |
1741 | + other_archive = self.factory.makeArchive() |
1742 | + self.assertDep('i386', 'foo', [], archive=other_archive) |
1743 | + |
1744 | + other_archive.addArchiveDependency( |
1745 | + self.archive, PackagePublishingPocket.RELEASE) |
1746 | + self.assertDep('i386', 'foo', [bins[0]], archive=other_archive) |
1747 | + |
1748 | + def test_obeys_dependency_pockets(self): |
1749 | + # Only packages published in a pocket matching the dependency should |
1750 | + # be found. |
1751 | + release_bins = self.publisher.getPubBinaries( |
1752 | + binaryname='foo-release', archive=self.archive, |
1753 | + status=PackagePublishingStatus.PUBLISHED) |
1754 | + updates_bins = self.publisher.getPubBinaries( |
1755 | + binaryname='foo-updates', archive=self.archive, |
1756 | + status=PackagePublishingStatus.PUBLISHED, |
1757 | + pocket=PackagePublishingPocket.UPDATES) |
1758 | + proposed_bins = self.publisher.getPubBinaries( |
1759 | + binaryname='foo-proposed', archive=self.archive, |
1760 | + status=PackagePublishingStatus.PUBLISHED, |
1761 | + pocket=PackagePublishingPocket.PROPOSED) |
1762 | + |
1763 | + # Temporarily turn our test PPA into a copy archive, so we can |
1764 | + # add non-RELEASE dependencies on it. |
1765 | + removeSecurityProxy(self.archive).purpose = ArchivePurpose.COPY |
1766 | + |
1767 | + other_archive = self.factory.makeArchive() |
1768 | + other_archive.addArchiveDependency( |
1769 | + self.archive, PackagePublishingPocket.UPDATES) |
1770 | + self.assertDep( |
1771 | + 'i386', 'foo-release', [release_bins[0]], archive=other_archive) |
1772 | + self.assertDep( |
1773 | + 'i386', 'foo-updates', [updates_bins[0]], archive=other_archive) |
1774 | + self.assertDep('i386', 'foo-proposed', [], archive=other_archive) |
1775 | + |
1776 | + other_archive.removeArchiveDependency(self.archive) |
1777 | + other_archive.addArchiveDependency( |
1778 | + self.archive, PackagePublishingPocket.PROPOSED) |
1779 | + self.assertDep( |
1780 | + 'i386', 'foo-proposed', [proposed_bins[0]], archive=other_archive) |
1781 | + |
1782 | + def test_obeys_dependency_components(self): |
1783 | + # Only packages published in a component matching the dependency |
1784 | + # should be found. |
1785 | + primary = self.archive.distribution.main_archive |
1786 | + main_bins = self.publisher.getPubBinaries( |
1787 | + binaryname='foo-main', archive=primary, component='main', |
1788 | + status=PackagePublishingStatus.PUBLISHED) |
1789 | + universe_bins = self.publisher.getPubBinaries( |
1790 | + binaryname='foo-universe', archive=primary, |
1791 | + component='universe', |
1792 | + status=PackagePublishingStatus.PUBLISHED) |
1793 | + |
1794 | + self.archive.addArchiveDependency( |
1795 | + primary, PackagePublishingPocket.RELEASE, |
1796 | + component=getUtility(IComponentSet)['main']) |
1797 | + self.assertDep('i386', 'foo-main', [main_bins[0]]) |
1798 | + self.assertDep('i386', 'foo-universe', []) |
1799 | + |
1800 | + self.archive.removeArchiveDependency(primary) |
1801 | + self.archive.addArchiveDependency( |
1802 | + primary, PackagePublishingPocket.RELEASE, |
1803 | + component=getUtility(IComponentSet)['universe']) |
1804 | + self.assertDep('i386', 'foo-main', [main_bins[0]]) |
1805 | + self.assertDep('i386', 'foo-universe', [universe_bins[0]]) |
1806 | +======= |
1807 | + |
1808 | + |
1809 | +class TestFindDepCandidates(TestCaseWithFactory): |
1810 | + """Tests for Archive.findDepCandidates.""" |
1811 | + |
1812 | + layer = LaunchpadZopelessLayer |
1813 | + |
1814 | + def setUp(self): |
1815 | + super(TestFindDepCandidates, self).setUp() |
1816 | + self.archive = self.factory.makeArchive() |
1817 | + self.publisher = SoyuzTestPublisher() |
1818 | + login('admin@canonical.com') |
1819 | + self.publisher.prepareBreezyAutotest() |
1820 | + |
1821 | + def assertDep(self, arch_tag, name, expected, archive=None, |
1822 | + pocket=PackagePublishingPocket.RELEASE, component=None, |
1823 | + source_package_name='something-new'): |
1824 | + """Helper to check that findDepCandidates works. |
1825 | + |
1826 | + Searches for the given dependency name in the given architecture and |
1827 | + archive, and compares it to the given expected value. |
1828 | + The archive defaults to self.archive. |
1829 | + |
1830 | + Also commits, since findDepCandidates uses the slave store. |
1831 | + """ |
1832 | + transaction.commit() |
1833 | + |
1834 | + if component is None: |
1835 | + component = getUtility(IComponentSet)['main'] |
1836 | + if archive is None: |
1837 | + archive = self.archive |
1838 | + |
1839 | + self.assertEquals( |
1840 | + list( |
1841 | + archive.findDepCandidates( |
1842 | + self.publisher.distroseries[arch_tag], pocket, component, |
1843 | + source_package_name, name)), |
1844 | + expected) |
1845 | + |
1846 | + def test_finds_candidate_in_same_archive(self): |
1847 | + # A published candidate in the same archive should be found. |
1848 | + bins = self.publisher.getPubBinaries( |
1849 | + binaryname='foo', archive=self.archive, |
1850 | + status=PackagePublishingStatus.PUBLISHED) |
1851 | + self.assertDep('i386', 'foo', [bins[0]]) |
1852 | + self.assertDep('hppa', 'foo', [bins[1]]) |
1853 | + |
1854 | + def test_does_not_find_pending_publication(self): |
1855 | + # A pending candidate in the same archive should not be found. |
1856 | + bins = self.publisher.getPubBinaries( |
1857 | + binaryname='foo', archive=self.archive) |
1858 | + self.assertDep('i386', 'foo', []) |
1859 | + |
1860 | + def test_ppa_searches_primary_archive(self): |
1861 | + # PPA searches implicitly look in the primary archive too. |
1862 | + self.assertEquals(self.archive.purpose, ArchivePurpose.PPA) |
1863 | + self.assertDep('i386', 'foo', []) |
1864 | + |
1865 | + bins = self.publisher.getPubBinaries( |
1866 | + binaryname='foo', archive=self.archive.distribution.main_archive, |
1867 | + status=PackagePublishingStatus.PUBLISHED) |
1868 | + |
1869 | + self.assertDep('i386', 'foo', [bins[0]]) |
1870 | + |
1871 | + def test_searches_dependencies(self): |
1872 | + # Candidates from archives on which the target explicitly depends |
1873 | + # should be found. |
1874 | + bins = self.publisher.getPubBinaries( |
1875 | + binaryname='foo', archive=self.archive, |
1876 | + status=PackagePublishingStatus.PUBLISHED) |
1877 | + other_archive = self.factory.makeArchive() |
1878 | + self.assertDep('i386', 'foo', [], archive=other_archive) |
1879 | + |
1880 | + other_archive.addArchiveDependency( |
1881 | + self.archive, PackagePublishingPocket.RELEASE) |
1882 | + self.assertDep('i386', 'foo', [bins[0]], archive=other_archive) |
1883 | + |
1884 | + def test_obeys_dependency_pockets(self): |
1885 | + # Only packages published in a pocket matching the dependency should |
1886 | + # be found. |
1887 | + release_bins = self.publisher.getPubBinaries( |
1888 | + binaryname='foo-release', archive=self.archive, |
1889 | + status=PackagePublishingStatus.PUBLISHED) |
1890 | + updates_bins = self.publisher.getPubBinaries( |
1891 | + binaryname='foo-updates', archive=self.archive, |
1892 | + status=PackagePublishingStatus.PUBLISHED, |
1893 | + pocket=PackagePublishingPocket.UPDATES) |
1894 | + proposed_bins = self.publisher.getPubBinaries( |
1895 | + binaryname='foo-proposed', archive=self.archive, |
1896 | + status=PackagePublishingStatus.PUBLISHED, |
1897 | + pocket=PackagePublishingPocket.PROPOSED) |
1898 | + |
1899 | + # Temporarily turn our test PPA into a copy archive, so we can |
1900 | + # add non-RELEASE dependencies on it. |
1901 | + removeSecurityProxy(self.archive).purpose = ArchivePurpose.COPY |
1902 | + |
1903 | + other_archive = self.factory.makeArchive() |
1904 | + other_archive.addArchiveDependency( |
1905 | + self.archive, PackagePublishingPocket.UPDATES) |
1906 | + self.assertDep( |
1907 | + 'i386', 'foo-release', [release_bins[0]], archive=other_archive) |
1908 | + self.assertDep( |
1909 | + 'i386', 'foo-updates', [updates_bins[0]], archive=other_archive) |
1910 | + self.assertDep('i386', 'foo-proposed', [], archive=other_archive) |
1911 | + |
1912 | + other_archive.removeArchiveDependency(self.archive) |
1913 | + other_archive.addArchiveDependency( |
1914 | + self.archive, PackagePublishingPocket.PROPOSED) |
1915 | + self.assertDep( |
1916 | + 'i386', 'foo-proposed', [proposed_bins[0]], archive=other_archive) |
1917 | + |
1918 | + def test_obeys_dependency_components(self): |
1919 | + # Only packages published in a component matching the dependency |
1920 | + # should be found. |
1921 | + primary = self.archive.distribution.main_archive |
1922 | + main_bins = self.publisher.getPubBinaries( |
1923 | + binaryname='foo-main', archive=primary, component='main', |
1924 | + status=PackagePublishingStatus.PUBLISHED) |
1925 | + universe_bins = self.publisher.getPubBinaries( |
1926 | + binaryname='foo-universe', archive=primary, |
1927 | + component='universe', |
1928 | + status=PackagePublishingStatus.PUBLISHED) |
1929 | + |
1930 | + self.archive.addArchiveDependency( |
1931 | + primary, PackagePublishingPocket.RELEASE, |
1932 | + component=getUtility(IComponentSet)['main']) |
1933 | + self.assertDep('i386', 'foo-main', [main_bins[0]]) |
1934 | + self.assertDep('i386', 'foo-universe', []) |
1935 | + |
1936 | + self.archive.removeArchiveDependency(primary) |
1937 | + self.archive.addArchiveDependency( |
1938 | + primary, PackagePublishingPocket.RELEASE, |
1939 | + component=getUtility(IComponentSet)['universe']) |
1940 | + self.assertDep('i386', 'foo-main', [main_bins[0]]) |
1941 | + self.assertDep('i386', 'foo-universe', [universe_bins[0]]) |
1942 | + |
1943 | + |
1944 | +def test_suite(): |
1945 | + return unittest.TestLoader().loadTestsFromName(__name__) |
1946 | +>>>>>>> MERGE-SOURCE |
1947 | |
1948 | === modified file 'lib/lp/soyuz/tests/test_archive_agent.py' |
1949 | === modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py' |
1950 | === modified file 'lib/lp/soyuz/tests/test_publishing.py' |
1951 | --- lib/lp/soyuz/tests/test_publishing.py 2010-07-23 10:58:48 +0000 |
1952 | +++ lib/lp/soyuz/tests/test_publishing.py 2010-07-29 19:06:17 +0000 |
1953 | @@ -961,6 +961,7 @@ |
1954 | self.assertEquals(self.sparc_distroarch, builds[1].distro_arch_series) |
1955 | |
1956 | |
1957 | +<<<<<<< TREE |
1958 | class PublishingSetTests(TestCaseWithFactory): |
1959 | |
1960 | layer = DatabaseFunctionalLayer |
1961 | @@ -1186,3 +1187,63 @@ |
1962 | # This will supersede both atomically. |
1963 | bins[0].supersede() |
1964 | self.checkOtherPublications(bins[0], []) |
1965 | +======= |
1966 | +class PublishingSetTests(TestCaseWithFactory): |
1967 | + |
1968 | + layer = DatabaseFunctionalLayer |
1969 | + |
1970 | + def setUp(self): |
1971 | + super(PublishingSetTests, self).setUp() |
1972 | + self.distroseries = self.factory.makeDistroSeries() |
1973 | + self.archive = self.factory.makeArchive( |
1974 | + distribution=self.distroseries.distribution) |
1975 | + self.publishing = self.factory.makeSourcePackagePublishingHistory( |
1976 | + distroseries=self.distroseries, archive=self.archive) |
1977 | + self.publishing_set = getUtility(IPublishingSet) |
1978 | + |
1979 | + def test_getByIdAndArchive_finds_record(self): |
1980 | + record = self.publishing_set.getByIdAndArchive( |
1981 | + self.publishing.id, self.archive) |
1982 | + self.assertEqual(self.publishing, record) |
1983 | + |
1984 | + def test_getByIdAndArchive_finds_record_explicit_source(self): |
1985 | + record = self.publishing_set.getByIdAndArchive( |
1986 | + self.publishing.id, self.archive, source=True) |
1987 | + self.assertEqual(self.publishing, record) |
1988 | + |
1989 | + def test_getByIdAndArchive_wrong_archive(self): |
1990 | + wrong_archive = self.factory.makeArchive() |
1991 | + record = self.publishing_set.getByIdAndArchive( |
1992 | + self.publishing.id, wrong_archive) |
1993 | + self.assertEqual(None, record) |
1994 | + |
1995 | + def makeBinaryPublishing(self): |
1996 | + distroarchseries = self.factory.makeDistroArchSeries( |
1997 | + distroseries=self.distroseries) |
1998 | + binary_publishing = self.factory.makeBinaryPackagePublishingHistory( |
1999 | + archive=self.archive, distroarchseries=distroarchseries) |
2000 | + return binary_publishing |
2001 | + |
2002 | + def test_getByIdAndArchive_wrong_type(self): |
2003 | + self.makeBinaryPublishing() |
2004 | + record = self.publishing_set.getByIdAndArchive( |
2005 | + self.publishing.id, self.archive, source=False) |
2006 | + self.assertEqual(None, record) |
2007 | + |
2008 | + def test_getByIdAndArchive_finds_binary(self): |
2009 | + binary_publishing = self.makeBinaryPublishing() |
2010 | + record = self.publishing_set.getByIdAndArchive( |
2011 | + binary_publishing.id, self.archive, source=False) |
2012 | + self.assertEqual(binary_publishing, record) |
2013 | + |
2014 | + def test_getByIdAndArchive_binary_wrong_archive(self): |
2015 | + binary_publishing = self.makeBinaryPublishing() |
2016 | + wrong_archive = self.factory.makeArchive() |
2017 | + record = self.publishing_set.getByIdAndArchive( |
2018 | + binary_publishing.id, wrong_archive, source=False) |
2019 | + self.assertEqual(None, record) |
2020 | + |
2021 | + |
2022 | +def test_suite(): |
2023 | + return unittest.TestLoader().loadTestsFromName(__name__) |
2024 | +>>>>>>> MERGE-SOURCE |
2025 | |
2026 | === modified file 'lib/lp/testing/__init__.py' |
2027 | === modified file 'lib/lp/testing/factory.py' |
2028 | --- lib/lp/testing/factory.py 2010-07-29 07:28:56 +0000 |
2029 | +++ lib/lp/testing/factory.py 2010-07-29 19:06:17 +0000 |
2030 | @@ -151,6 +151,7 @@ |
2031 | from lp.soyuz.interfaces.publishing import ( |
2032 | PackagePublishingPriority, PackagePublishingStatus) |
2033 | from lp.soyuz.interfaces.section import ISectionSet |
2034 | +<<<<<<< TREE |
2035 | from lp.soyuz.model.binarypackagename import BinaryPackageName |
2036 | from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease |
2037 | from lp.soyuz.model.processor import ProcessorFamilySet |
2038 | @@ -164,6 +165,22 @@ |
2039 | temp_dir, |
2040 | time_counter, |
2041 | ) |
2042 | +======= |
2043 | +from lp.soyuz.model.binarypackagename import BinaryPackageName |
2044 | +from lp.soyuz.model.binarypackagerelease import BinaryPackageRelease |
2045 | +from lp.soyuz.model.processor import ProcessorFamily, ProcessorFamilySet |
2046 | +from lp.soyuz.model.publishing import ( |
2047 | + BinaryPackagePublishingHistory, SourcePackagePublishingHistory) |
2048 | +from lp.testing import ( |
2049 | + ANONYMOUS, |
2050 | + login, |
2051 | + login_as, |
2052 | + logout, |
2053 | + run_with_login, |
2054 | + temp_dir, |
2055 | + time_counter, |
2056 | + ) |
2057 | +>>>>>>> MERGE-SOURCE |
2058 | from lp.translations.interfaces.potemplate import IPOTemplateSet |
2059 | from lp.translations.interfaces.translationimportqueue import ( |
2060 | RosettaImportStatus) |
2061 | |
2062 | === modified file 'lib/lp/testing/tests/test_factory.py' |
2063 | --- lib/lp/testing/tests/test_factory.py 2010-07-26 12:58:13 +0000 |
2064 | +++ lib/lp/testing/tests/test_factory.py 2010-07-29 19:06:17 +0000 |
2065 | @@ -7,10 +7,16 @@ |
2066 | |
2067 | import unittest |
2068 | |
2069 | +<<<<<<< TREE |
2070 | from zope.component import getUtility |
2071 | from zope.security.proxy import removeSecurityProxy |
2072 | |
2073 | from canonical.launchpad.webapp.interfaces import ILaunchBag |
2074 | +======= |
2075 | +from zope.component import getUtility |
2076 | + |
2077 | +from canonical.launchpad.webapp.interfaces import ILaunchBag |
2078 | +>>>>>>> MERGE-SOURCE |
2079 | from canonical.testing.layers import DatabaseFunctionalLayer |
2080 | from lp.code.enums import CodeImportReviewStatus |
2081 | from lp.testing import TestCaseWithFactory |
2082 | @@ -35,6 +41,7 @@ |
2083 | code_import = self.factory.makeCodeImport(review_status=status) |
2084 | self.assertEqual(status, code_import.review_status) |
2085 | |
2086 | +<<<<<<< TREE |
2087 | def test_makeLanguage(self): |
2088 | # Without parameters, makeLanguage creates a language with code |
2089 | # starting with 'lang'. |
2090 | @@ -116,6 +123,15 @@ |
2091 | self.assertFalse( |
2092 | is_security_proxied_or_harmless([1, '2', unproxied_person])) |
2093 | |
2094 | +======= |
2095 | + def test_loginAsAnyone(self): |
2096 | + # Login as anyone logs you in as any user. |
2097 | + person = self.factory.loginAsAnyone() |
2098 | + current_person = getUtility(ILaunchBag).user |
2099 | + self.assertIsNot(None, person) |
2100 | + self.assertEqual(person, current_person) |
2101 | + |
2102 | +>>>>>>> MERGE-SOURCE |
2103 | |
2104 | def test_suite(): |
2105 | return unittest.TestLoader().loadTestsFromName(__name__) |
2106 | |
2107 | === modified file 'lib/lp/translations/browser/potemplate.py' |
2108 | === modified file 'lib/lp/translations/interfaces/potemplate.py' |
2109 | --- lib/lp/translations/interfaces/potemplate.py 2010-07-23 11:35:07 +0000 |
2110 | +++ lib/lp/translations/interfaces/potemplate.py 2010-07-29 19:06:17 +0000 |
2111 | @@ -774,6 +774,7 @@ |
2112 | """A list of native formats for all current translation templates. |
2113 | """ |
2114 | |
2115 | +<<<<<<< TREE |
2116 | def getTemplatesAndLanguageCounts(): |
2117 | """List tuples of `POTemplate` and its language count. |
2118 | |
2119 | @@ -795,5 +796,14 @@ |
2120 | """Return a ResultSet for this collection with values set to args.""" |
2121 | |
2122 | |
2123 | +======= |
2124 | + def getTemplatesAndLanguageCounts(): |
2125 | + """List tuples of `POTemplate` and its language count. |
2126 | + |
2127 | + A template's language count is the number of `POFile`s that |
2128 | + exist for it. |
2129 | + """ |
2130 | + |
2131 | +>>>>>>> MERGE-SOURCE |
2132 | # Monkey patch for circular import avoidance done in |
2133 | # _schema_circular_imports.py |
2134 | |
2135 | === modified file 'lib/lp/translations/javascript/importqueue.js' |
2136 | === modified file 'lib/lp/translations/model/potemplate.py' |
2137 | --- lib/lp/translations/model/potemplate.py 2010-07-26 12:58:13 +0000 |
2138 | +++ lib/lp/translations/model/potemplate.py 2010-07-29 19:06:17 +0000 |
2139 | @@ -461,7 +461,12 @@ |
2140 | "POFile.potemplate = %d AND " |
2141 | "POFile.variant IS NULL" % self.id, |
2142 | clauseTables=['POFile', 'Language'], |
2143 | +<<<<<<< TREE |
2144 | distinct=True) |
2145 | +======= |
2146 | + distinct=True, |
2147 | + ) |
2148 | +>>>>>>> MERGE-SOURCE |
2149 | |
2150 | def getPOFileByPath(self, path): |
2151 | """See `IPOTemplate`.""" |
2152 | @@ -1559,9 +1564,14 @@ |
2153 | |
2154 | @property |
2155 | def has_current_translation_templates(self): |
2156 | +<<<<<<< TREE |
2157 | """See `IHasTranslationTemplates`.""" |
2158 | return bool( |
2159 | self.getCurrentTranslationTemplates(just_ids=True).any()) |
2160 | +======= |
2161 | + """See `IHasTranslationTemplates`.""" |
2162 | + return bool(self.getCurrentTranslationTemplates(just_ids=True).any()) |
2163 | +>>>>>>> MERGE-SOURCE |
2164 | |
2165 | def getCurrentTranslationFiles(self, just_ids=False): |
2166 | """See `IHasTranslationTemplates`.""" |
2167 | @@ -1588,6 +1598,7 @@ |
2168 | """See `IHasTranslationTemplates`.""" |
2169 | formats_query = self.getCurrentTranslationTemplates().order_by( |
2170 | 'source_file_format').config(distinct=True) |
2171 | +<<<<<<< TREE |
2172 | return helpers.shortlist( |
2173 | formats_query.values(POTemplate.source_file_format), 10) |
2174 | |
2175 | @@ -1669,3 +1680,80 @@ |
2176 | else: |
2177 | return self.joinOuter( |
2178 | POFile, POTemplate.id == POFile.potemplateID) |
2179 | +======= |
2180 | + return helpers.shortlist( |
2181 | + formats_query.values(POTemplate.source_file_format), 10) |
2182 | + |
2183 | + def getTemplatesAndLanguageCounts(self): |
2184 | + """See `IHasTranslationTemplates`.""" |
2185 | + join = self.getTemplatesCollection().joinOuterPOFile() |
2186 | + result = join.select(POTemplate, Count(POFile.id)) |
2187 | + return result.group_by(POTemplate) |
2188 | + |
2189 | + |
2190 | +class TranslationTemplatesCollection(Collection): |
2191 | + """A `Collection` of `POTemplate`.""" |
2192 | + starting_table = POTemplate |
2193 | + |
2194 | + # The Product or Distribution that this collection is restricted to. |
2195 | + target_pillar = None |
2196 | + |
2197 | + def __init__(self, *args, **kwargs): |
2198 | + super(TranslationTemplatesCollection, self).__init__(*args, **kwargs) |
2199 | + if self.base is not None: |
2200 | + self.target_pillar = self.base.target_pillar |
2201 | + |
2202 | + def restrictProductSeries(self, productseries): |
2203 | + product = productseries.product |
2204 | + new_collection = self.refine( |
2205 | + POTemplate.productseriesID == productseries.id) |
2206 | + new_collection._setTargetPillar(product) |
2207 | + return new_collection |
2208 | + |
2209 | + def restrictDistroSeries(self, distroseries): |
2210 | + distribution = distroseries.distribution |
2211 | + new_collection = self.refine( |
2212 | + POTemplate.distroseriesID == distroseries.id) |
2213 | + new_collection._setTargetPillar(distribution) |
2214 | + return new_collection |
2215 | + |
2216 | + def restrictSourcePackageName(self, sourcepackagename): |
2217 | + return self.refine( |
2218 | + POTemplate.sourcepackagenameID == sourcepackagename.id) |
2219 | + |
2220 | + def _setTargetPillar(self, target_pillar): |
2221 | + assert ( |
2222 | + self.target_pillar is None or |
2223 | + self.target_pillar == target_pillar), ( |
2224 | + "Collection restricted to both %s and %s." % ( |
2225 | + self.target_pillar, target_pillar)) |
2226 | + self.target_pillar = target_pillar |
2227 | + |
2228 | + def restrictCurrent(self, current_value=True): |
2229 | + """Select based on `POTemplate.iscurrent`. |
2230 | + |
2231 | + :param current_value: The value for `iscurrent` that you are |
2232 | + looking for. Defaults to True, meaning this will restrict |
2233 | + to current templates. If False, will select obsolete |
2234 | + templates instead. |
2235 | + :return: A `TranslationTemplatesCollection` based on this one, |
2236 | + but restricted to ones with the desired `iscurrent` value. |
2237 | + """ |
2238 | + return self.refine(POTemplate.iscurrent == current_value) |
2239 | + |
2240 | + def joinPOFile(self): |
2241 | + """Join `POFile` into the collection. |
2242 | + |
2243 | + :return: A `TranslationTemplatesCollection` with an added inner |
2244 | + join to `POFile`. |
2245 | + """ |
2246 | + return self.joinInner(POFile, POTemplate.id == POFile.potemplateID) |
2247 | + |
2248 | + def joinOuterPOFile(self): |
2249 | + """Outer-join `POFile` into the collection. |
2250 | + |
2251 | + :return: A `TranslationTemplatesCollection` with an added outer |
2252 | + join to `POFile`. |
2253 | + """ |
2254 | + return self.joinOuter(POFile, POTemplate.id == POFile.potemplateID) |
2255 | +>>>>>>> MERGE-SOURCE |
2256 | |
2257 | === modified file 'lib/lp/translations/tests/test_translationtemplatescollection.py' |
2258 | --- lib/lp/translations/tests/test_translationtemplatescollection.py 2010-07-23 11:33:17 +0000 |
2259 | +++ lib/lp/translations/tests/test_translationtemplatescollection.py 2010-07-29 19:06:17 +0000 |
2260 | @@ -1,3 +1,4 @@ |
2261 | +<<<<<<< TREE |
2262 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
2263 | # GNU Affero General Public License version 3 (see the file LICENSE). |
2264 | |
2265 | @@ -224,3 +225,212 @@ |
2266 | ] |
2267 | self.assertContentEqual( |
2268 | expected_outcome, joined.select(POTemplate, POFile)) |
2269 | +======= |
2270 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
2271 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2272 | + |
2273 | +"""Test `TranslationTemplatesCollection`.""" |
2274 | + |
2275 | +__metaclass__ = type |
2276 | + |
2277 | +from zope.security.proxy import removeSecurityProxy |
2278 | +from canonical.testing import DatabaseFunctionalLayer |
2279 | +from lp.testing import TestCaseWithFactory |
2280 | + |
2281 | +from lp.translations.model.pofile import POFile |
2282 | +from lp.translations.model.potemplate import ( |
2283 | + POTemplate, |
2284 | + TranslationTemplatesCollection) |
2285 | + |
2286 | + |
2287 | +class TestSomething(TestCaseWithFactory): |
2288 | + layer = DatabaseFunctionalLayer |
2289 | + |
2290 | + def test_baseline(self): |
2291 | + # A collection constructed with no arguments selects all |
2292 | + # templates. |
2293 | + template = self.factory.makePOTemplate() |
2294 | + |
2295 | + collection = TranslationTemplatesCollection() |
2296 | + self.assertIn(template, collection.select()) |
2297 | + |
2298 | + def test_none_found(self): |
2299 | + trunk = self.factory.makeProduct().getSeries('trunk') |
2300 | + collection = TranslationTemplatesCollection() |
2301 | + by_series = collection.restrictProductSeries(trunk) |
2302 | + |
2303 | + self.assertContentEqual([], by_series.select()) |
2304 | + |
2305 | + def test_restrictProductSeries(self): |
2306 | + trunk = self.factory.makeProduct().getSeries('trunk') |
2307 | + template = self.factory.makePOTemplate(productseries=trunk) |
2308 | + |
2309 | + collection = TranslationTemplatesCollection() |
2310 | + by_series = collection.restrictProductSeries(trunk) |
2311 | + |
2312 | + self.assertContentEqual([template], by_series.select()) |
2313 | + |
2314 | + def test_restrictProductSeries_restricts(self): |
2315 | + # restrictProductSeries makes the collection ignore templates |
2316 | + # from other productseries and source packages. |
2317 | + product = self.factory.makeProduct() |
2318 | + trunk = product.getSeries('trunk') |
2319 | + |
2320 | + nontrunk = removeSecurityProxy(product).newSeries( |
2321 | + product.owner, 'nontrunk', 'foo') |
2322 | + sourcepackage = self.factory.makeSourcePackage() |
2323 | + |
2324 | + self.factory.makePOTemplate(productseries=nontrunk) |
2325 | + self.factory.makePOTemplate( |
2326 | + distroseries=sourcepackage.distroseries, |
2327 | + sourcepackagename=sourcepackage.sourcepackagename) |
2328 | + |
2329 | + collection = TranslationTemplatesCollection() |
2330 | + by_series = collection.restrictProductSeries(trunk) |
2331 | + |
2332 | + self.assertContentEqual([], by_series.select()) |
2333 | + |
2334 | + def test_restrictDistroSeries(self): |
2335 | + package = self.factory.makeSourcePackage() |
2336 | + template = self.factory.makePOTemplate( |
2337 | + distroseries=package.distroseries, |
2338 | + sourcepackagename=package.sourcepackagename) |
2339 | + |
2340 | + collection = TranslationTemplatesCollection() |
2341 | + by_series = collection.restrictDistroSeries(package.distroseries) |
2342 | + |
2343 | + self.assertContentEqual([template], by_series.select()) |
2344 | + |
2345 | + def test_restrictDistroSeries_restricts(self): |
2346 | + # restrictProductSeries makes the collection ignore templates |
2347 | + # from other productseries and distroseries. |
2348 | + distribution = self.factory.makeDistribution() |
2349 | + series = self.factory.makeDistroSeries(distribution=distribution) |
2350 | + other_series = self.factory.makeDistroSeries( |
2351 | + distribution=distribution) |
2352 | + productseries = self.factory.makeProductSeries() |
2353 | + package = self.factory.makeSourcePackageName() |
2354 | + |
2355 | + self.factory.makePOTemplate( |
2356 | + distroseries=other_series, sourcepackagename=package) |
2357 | + self.factory.makePOTemplate(productseries=productseries) |
2358 | + |
2359 | + collection = TranslationTemplatesCollection() |
2360 | + by_series = collection.restrictDistroSeries(series) |
2361 | + |
2362 | + self.assertContentEqual([], by_series.select()) |
2363 | + |
2364 | + def test_restrictSourcePackageName(self): |
2365 | + package = self.factory.makeSourcePackage() |
2366 | + template = self.factory.makePOTemplate( |
2367 | + distroseries=package.distroseries, |
2368 | + sourcepackagename=package.sourcepackagename) |
2369 | + |
2370 | + assert package.sourcepackagename |
2371 | + collection = TranslationTemplatesCollection() |
2372 | + by_packagename = collection.restrictSourcePackageName( |
2373 | + package.sourcepackagename) |
2374 | + |
2375 | + self.assertContentEqual([template], by_packagename.select()) |
2376 | + |
2377 | + def test_restrictSourcePackageName_restricts(self): |
2378 | + # restrictSourcePackageName makes the collection ignore |
2379 | + # templates from other source package names and productseries. |
2380 | + package = self.factory.makeSourcePackage() |
2381 | + distroseries = package.distroseries |
2382 | + other_package = self.factory.makeSourcePackage( |
2383 | + distroseries=distroseries) |
2384 | + productseries = self.factory.makeProductSeries() |
2385 | + |
2386 | + self.factory.makePOTemplate( |
2387 | + distroseries=distroseries, |
2388 | + sourcepackagename=other_package.sourcepackagename) |
2389 | + self.factory.makePOTemplate(productseries=productseries) |
2390 | + |
2391 | + collection = TranslationTemplatesCollection() |
2392 | + by_packagename = collection.restrictSourcePackageName( |
2393 | + package.sourcepackagename) |
2394 | + |
2395 | + self.assertContentEqual([], by_packagename.select()) |
2396 | + |
2397 | + def test_restrict_SourcePackage(self): |
2398 | + # You can restrict to a source package by restricting both to a |
2399 | + # DistroSeries and to a SourcePackageName. |
2400 | + package = self.factory.makeSourcePackage() |
2401 | + template = self.factory.makePOTemplate( |
2402 | + distroseries=package.distroseries, |
2403 | + sourcepackagename=package.sourcepackagename) |
2404 | + |
2405 | + collection = TranslationTemplatesCollection() |
2406 | + by_series = collection.restrictDistroSeries(package.distroseries) |
2407 | + by_package = by_series.restrictSourcePackageName( |
2408 | + package.sourcepackagename) |
2409 | + |
2410 | + self.assertContentEqual([template], by_package.select()) |
2411 | + |
2412 | + def test_restrictCurrent_current(self): |
2413 | + trunk = self.factory.makeProduct().getSeries('trunk') |
2414 | + template = self.factory.makePOTemplate(productseries=trunk) |
2415 | + collection = TranslationTemplatesCollection() |
2416 | + by_series = collection.restrictProductSeries(trunk) |
2417 | + |
2418 | + current_templates = by_series.restrictCurrent(True) |
2419 | + |
2420 | + removeSecurityProxy(template).iscurrent = True |
2421 | + self.assertContentEqual( |
2422 | + [template], current_templates.select()) |
2423 | + |
2424 | + removeSecurityProxy(template).iscurrent = False |
2425 | + self.assertContentEqual([], current_templates.select()) |
2426 | + |
2427 | + def test_restrictCurrent_obsolete(self): |
2428 | + trunk = self.factory.makeProduct().getSeries('trunk') |
2429 | + template = self.factory.makePOTemplate(productseries=trunk) |
2430 | + collection = TranslationTemplatesCollection() |
2431 | + by_series = collection.restrictProductSeries(trunk) |
2432 | + |
2433 | + obsolete_templates = by_series.restrictCurrent(False) |
2434 | + |
2435 | + removeSecurityProxy(template).iscurrent = True |
2436 | + self.assertContentEqual([], obsolete_templates.select()) |
2437 | + |
2438 | + removeSecurityProxy(template).iscurrent = False |
2439 | + self.assertContentEqual( |
2440 | + [template], obsolete_templates.select()) |
2441 | + |
2442 | + def test_joinPOFile(self): |
2443 | + trunk = self.factory.makeProduct().getSeries('trunk') |
2444 | + translated_template = self.factory.makePOTemplate(productseries=trunk) |
2445 | + untranslated_template = self.factory.makePOTemplate( |
2446 | + productseries=trunk) |
2447 | + nl = translated_template.newPOFile('nl') |
2448 | + de = translated_template.newPOFile('de') |
2449 | + |
2450 | + collection = TranslationTemplatesCollection() |
2451 | + by_series = collection.restrictProductSeries(trunk) |
2452 | + joined = by_series.joinPOFile() |
2453 | + |
2454 | + self.assertContentEqual( |
2455 | + [(translated_template, nl), (translated_template, de)], |
2456 | + joined.select(POTemplate, POFile)) |
2457 | + |
2458 | + def test_joinOuterPOFile(self): |
2459 | + trunk = self.factory.makeProduct().getSeries('trunk') |
2460 | + translated_template = self.factory.makePOTemplate(productseries=trunk) |
2461 | + untranslated_template = self.factory.makePOTemplate( |
2462 | + productseries=trunk) |
2463 | + nl = translated_template.newPOFile('nl') |
2464 | + de = translated_template.newPOFile('de') |
2465 | + |
2466 | + collection = TranslationTemplatesCollection() |
2467 | + by_series = collection.restrictProductSeries(trunk) |
2468 | + joined = by_series.joinOuterPOFile() |
2469 | + |
2470 | + expected_outcome = [ |
2471 | + (translated_template, nl), |
2472 | + (translated_template, de), |
2473 | + (untranslated_template, None), |
2474 | + ] |
2475 | + self.assertContentEqual( |
2476 | + expected_outcome, joined.select(POTemplate, POFile)) |
2477 | +>>>>>>> MERGE-SOURCE |
2478 | |
2479 | === modified file 'lib/lp/translations/utilities/gettext_mo_exporter.py' |
2480 | --- lib/lp/translations/utilities/gettext_mo_exporter.py 2010-07-23 15:26:56 +0000 |
2481 | +++ lib/lp/translations/utilities/gettext_mo_exporter.py 2010-07-29 19:06:17 +0000 |
2482 | @@ -74,6 +74,7 @@ |
2483 | translation_exporter.getExporterProducingTargetFileFormat( |
2484 | TranslationFileFormat.PO)) |
2485 | |
2486 | +<<<<<<< TREE |
2487 | # To generate MO files we need first its PO version and then, |
2488 | # generate the MO one. |
2489 | temp_storage = ExportFileStorage() |
2490 | @@ -107,3 +108,43 @@ |
2491 | |
2492 | storage.addFile( |
2493 | file_path, file_extension, exported_file_content, content_type) |
2494 | +======= |
2495 | + mime_type = 'application/x-gmo' |
2496 | + storage = ExportFileStorage() |
2497 | + |
2498 | + for translation_file in translation_files: |
2499 | + # To generate MO files we need first its PO version and then, |
2500 | + # generate the MO one. |
2501 | + template_exported = gettext_po_exporter.exportTranslationFiles( |
2502 | + [translation_file], ignore_obsolete, force_utf8) |
2503 | + exported_file_content = template_exported.read() |
2504 | + if translation_file.is_template: |
2505 | + # This exporter is not able to handle template files. In that |
2506 | + # case, we leave it as .po file. For this file format exported |
2507 | + # templates are stored in templates/ directory. |
2508 | + file_path = 'templates/%s' % os.path.basename( |
2509 | + template_exported.path) |
2510 | + content_type = template_exported.content_type |
2511 | + file_extension = template_exported.file_extension |
2512 | + else: |
2513 | + file_extension = 'mo' |
2514 | + # Standard layout for MO files is |
2515 | + # 'LANG_CODE/LC_MESSAGES/TRANSLATION_DOMAIN.mo' |
2516 | + file_path = os.path.join( |
2517 | + translation_file.language_code, |
2518 | + 'LC_MESSAGES', |
2519 | + '%s.%s' % ( |
2520 | + translation_file.translation_domain, |
2521 | + file_extension)) |
2522 | + mo_compiler = POCompiler() |
2523 | + mo_content = mo_compiler.compile(exported_file_content) |
2524 | + exported_file_content = mo_content |
2525 | + # We use x-gmo for consistency with other .po editors like |
2526 | + # GTranslator. |
2527 | + content_type = 'application/x-gmo' |
2528 | + |
2529 | + storage.addFile( |
2530 | + file_path, file_extension, exported_file_content, mime_type) |
2531 | + |
2532 | + return storage.export() |
2533 | +>>>>>>> MERGE-SOURCE |
2534 | |
2535 | === modified file 'lib/lp/translations/utilities/gettext_po_exporter.py' |
2536 | --- lib/lp/translations/utilities/gettext_po_exporter.py 2010-07-23 15:26:56 +0000 |
2537 | +++ lib/lp/translations/utilities/gettext_po_exporter.py 2010-07-29 19:06:17 +0000 |
2538 | @@ -298,6 +298,7 @@ |
2539 | def exportTranslationFile(self, translation_file, storage, |
2540 | ignore_obsolete=False, force_utf8=False): |
2541 | """See `ITranslationFormatExporter`.""" |
2542 | +<<<<<<< TREE |
2543 | mime_type = 'application/x-po' |
2544 | |
2545 | dirname = os.path.dirname(translation_file.path) |
2546 | @@ -332,6 +333,53 @@ |
2547 | # Suppress messages that are duplicative to |
2548 | # gettext so that gettext doesn't choke on the |
2549 | # resulting file. |
2550 | +======= |
2551 | + mime_type = 'application/x-po' |
2552 | + storage = ExportFileStorage() |
2553 | + |
2554 | + for translation_file in translation_files: |
2555 | + dirname = os.path.dirname(translation_file.path) |
2556 | + if dirname == '': |
2557 | + # There is no directory in the path. Use |
2558 | + # translation_domain as its directory. |
2559 | + dirname = translation_file.translation_domain |
2560 | + |
2561 | + if translation_file.is_template: |
2562 | + file_extension = 'pot' |
2563 | + file_path = os.path.join( |
2564 | + dirname, '%s.%s' % ( |
2565 | + translation_file.translation_domain, |
2566 | + file_extension)) |
2567 | + else: |
2568 | + file_extension = 'po' |
2569 | + file_path = os.path.join( |
2570 | + dirname, '%s-%s.%s' % ( |
2571 | + translation_file.translation_domain, |
2572 | + translation_file.language_code, |
2573 | + file_extension)) |
2574 | + |
2575 | + chunks = [] |
2576 | + seen_keys = {} |
2577 | + |
2578 | + for message in translation_file.messages: |
2579 | + key = (message.context, message.msgid_singular) |
2580 | + if key in seen_keys: |
2581 | + # Launchpad can deal with messages that are |
2582 | + # identical to gettext, but differ in plural msgid. |
2583 | + plural = message.msgid_plural |
2584 | + previous_plural = seen_keys[key].msgid_plural |
2585 | + |
2586 | + if not self.msgid_plural_distinguishes_messages: |
2587 | + # Suppress messages that are duplicative to |
2588 | + # gettext so that gettext doesn't choke on the |
2589 | + # resulting file. |
2590 | + continue |
2591 | + else: |
2592 | + seen_keys[key] = message |
2593 | + |
2594 | + if (message.is_obsolete and |
2595 | + (ignore_obsolete or len(message.translations) == 0)): |
2596 | +>>>>>>> MERGE-SOURCE |
2597 | continue |
2598 | else: |
2599 | seen_keys[key] = message |
2600 | @@ -373,8 +421,15 @@ |
2601 | encoded_file_content = self._encode_file_content( |
2602 | translation_file, exported_file_content) |
2603 | |
2604 | +<<<<<<< TREE |
2605 | storage.addFile( |
2606 | file_path, file_extension, encoded_file_content, mime_type) |
2607 | +======= |
2608 | + storage.addFile( |
2609 | + file_path, file_extension, encoded_file_content, mime_type) |
2610 | + |
2611 | + return storage.export() |
2612 | +>>>>>>> MERGE-SOURCE |
2613 | |
2614 | def acceptSingularClash(self, previous_message, current_message): |
2615 | """Handle clash of (singular) msgid and context with other message. |
2616 | |
2617 | === modified file 'lib/lp/translations/utilities/tests/test_export_file_storage.py' |
2618 | --- lib/lp/translations/utilities/tests/test_export_file_storage.py 2010-07-23 19:44:16 +0000 |
2619 | +++ lib/lp/translations/utilities/tests/test_export_file_storage.py 2010-07-29 19:06:17 +0000 |
2620 | @@ -69,3 +69,12 @@ |
2621 | elements = set(tarball.getnames()) |
2622 | self.assertTrue('/tmp/a/test/file.po' in elements) |
2623 | self.assertTrue('/tmp/another/test.po' in elements) |
2624 | +<<<<<<< TREE |
2625 | +======= |
2626 | + |
2627 | + |
2628 | +def test_suite(): |
2629 | + suite = unittest.TestSuite() |
2630 | + suite.addTest(unittest.makeSuite(ExportFileStorageTestCase)) |
2631 | + return suite |
2632 | +>>>>>>> MERGE-SOURCE |
2633 | |
2634 | === modified file 'lib/lp/translations/utilities/translation_export.py' |
2635 | --- lib/lp/translations/utilities/translation_export.py 2010-07-23 19:44:16 +0000 |
2636 | +++ lib/lp/translations/utilities/translation_export.py 2010-07-29 19:06:17 +0000 |
2637 | @@ -198,8 +198,13 @@ |
2638 | Storage for single files is implemented by `SingleFileStorageStrategy`; |
2639 | multiple files go into a `TarballFileStorageStrategy`. |
2640 | """ |
2641 | +<<<<<<< TREE |
2642 | |
2643 | def addFile(self, path, extension, content, mime_type): |
2644 | +======= |
2645 | + |
2646 | + def addFile(self, path, extension, content, content_type): |
2647 | +>>>>>>> MERGE-SOURCE |
2648 | """Add a file to be stored.""" |
2649 | raise NotImplementedError() |
2650 | |
2651 | |
2652 | === modified file 'setup.py' |
2653 | --- setup.py 2010-07-23 08:50:49 +0000 |
2654 | +++ setup.py 2010-07-29 19:06:17 +0000 |
2655 | @@ -29,9 +29,14 @@ |
2656 | 'chameleon.core', |
2657 | 'chameleon.zpt', |
2658 | 'cssutils', |
2659 | +<<<<<<< TREE |
2660 | # Required for pydkim |
2661 | 'dnspython', |
2662 | 'FeedParser', |
2663 | +======= |
2664 | + # Required for pydkim |
2665 | + 'dnspython', |
2666 | +>>>>>>> MERGE-SOURCE |
2667 | 'feedvalidator', |
2668 | 'funkload', |
2669 | 'launchpadlib', |
2670 | |
2671 | === modified file 'versions.cfg' |
2672 | --- versions.cfg 2010-07-26 16:00:01 +0000 |
2673 | +++ versions.cfg 2010-07-29 19:06:17 +0000 |
2674 | @@ -36,8 +36,13 @@ |
2675 | lazr.smtptest = 1.1 |
2676 | lazr.testing = 0.1.1 |
2677 | lazr.uri = 1.0.2 |
2678 | +<<<<<<< TREE |
2679 | lazr-js = 1.0beta2 |
2680 | manuel = 1.1.1 |
2681 | +======= |
2682 | +lazr-js = 0.9.2DEVr170 |
2683 | +manuel = 1.1.1 |
2684 | +>>>>>>> MERGE-SOURCE |
2685 | martian = 0.11 |
2686 | mechanize = 0.1.11 |
2687 | meliae = 0.2.0.final.0 |
Great.