Merge lp:~jml/launchpad/branch-sample-data-doctests into lp:launchpad

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
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_status was in the public interface but did not have view access granted.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

Great.

review: Approve

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