Merge lp:~cjwatson/launchpad/delete-unmodifiable-suite into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 16526
Proposed branch: lp:~cjwatson/launchpad/delete-unmodifiable-suite
Merge into: lp:launchpad
Diff against target: 694 lines (+125/-125)
4 files modified
lib/lp/soyuz/interfaces/publishing.py (+9/-6)
lib/lp/soyuz/model/publishing.py (+30/-22)
lib/lp/soyuz/tests/test_publishing.py (+84/-95)
lib/lp/testing/factory.py (+2/-2)
To merge this branch: bzr merge lp:~cjwatson/launchpad/delete-unmodifiable-suite
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+152529@code.launchpad.net

Commit message

Refuse to delete packages from unmodifiable suites.

Description of the change

== Summary ==

Bug 1152669: it's possible to delete packages from a release pocket in a stable distroseries, requiring (AFAICT) DB surgery to recover. This shouldn't be permitted since the deletion will never be published anyway.

== Proposed fix ==

Add Archive.canModifySuite checks to the relevant methods.

== Implementation details ==

I may have gone a little overboard to achieve LoC neutrality. You can always just look at the principal effective revision (http://bazaar.launchpad.net/~cjwatson/launchpad/delete-unmodifiable-suite/revision/16526).

== Tests ==

bin/test -vvct lp.soyuz.tests.test_publishing

== Demo and Q/A ==

Try to delete a package from the quantal RELEASE pocket on qastaging (using the requestDeletion API method). This should fail with an error.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/interfaces/publishing.py'
--- lib/lp/soyuz/interfaces/publishing.py 2013-02-28 09:41:40 +0000
+++ lib/lp/soyuz/interfaces/publishing.py 2013-03-08 22:44:24 +0000
@@ -6,6 +6,7 @@
6__metaclass__ = type6__metaclass__ = type
77
8__all__ = [8__all__ = [
9 'DeletionError',
9 'IArchiveSafePublisher',10 'IArchiveSafePublisher',
10 'IBinaryPackageFilePublishing',11 'IBinaryPackageFilePublishing',
11 'IBinaryPackagePublishingHistory',12 'IBinaryPackagePublishingHistory',
@@ -106,6 +107,11 @@
106 """Raised when an attempt to change an override fails."""107 """Raised when an attempt to change an override fails."""
107108
108109
110@error_status(httplib.BAD_REQUEST)
111class DeletionError(Exception):
112 """Raised when an attempt to delete a publication fails."""
113
114
109name_priority_map = {115name_priority_map = {
110 'required': PackagePublishingPriority.REQUIRED,116 'required': PackagePublishingPriority.REQUIRED,
111 'important': PackagePublishingPriority.IMPORTANT,117 'important': PackagePublishingPriority.IMPORTANT,
@@ -284,19 +290,16 @@
284 title=_('Component name'), required=True, readonly=True,290 title=_('Component name'), required=True, readonly=True,
285 )291 )
286 publishingstatus = Int(292 publishingstatus = Int(
287 title=_('Package publishing status'), required=True,293 title=_('Package publishing status'), required=True, readonly=True,
288 readonly=True,
289 )294 )
290 pocket = Int(295 pocket = Int(
291 title=_('Package publishing pocket'), required=True,296 title=_('Package publishing pocket'), required=True, readonly=True,
292 readonly=True,
293 )297 )
294 archive = Int(298 archive = Int(
295 title=_('Archive ID'), required=True, readonly=True,299 title=_('Archive ID'), required=True, readonly=True,
296 )300 )
297 libraryfilealias = Int(301 libraryfilealias = Int(
298 title=_('Binarypackage file alias'), required=True,302 title=_('Binarypackage file alias'), required=True, readonly=True,
299 readonly=True,
300 )303 )
301 libraryfilealiasfilename = TextLine(304 libraryfilealiasfilename = TextLine(
302 title=_('File name'), required=True, readonly=True,305 title=_('File name'), required=True, readonly=True,
303306
=== modified file 'lib/lp/soyuz/model/publishing.py'
--- lib/lp/soyuz/model/publishing.py 2013-02-28 09:41:40 +0000
+++ lib/lp/soyuz/model/publishing.py 2013-03-08 22:44:24 +0000
@@ -91,6 +91,7 @@
91 )91 )
92from lp.soyuz.interfaces.publishing import (92from lp.soyuz.interfaces.publishing import (
93 active_publishing_status,93 active_publishing_status,
94 DeletionError,
94 IBinaryPackageFilePublishing,95 IBinaryPackageFilePublishing,
95 IBinaryPackagePublishingHistory,96 IBinaryPackagePublishingHistory,
96 IPublishingSet,97 IPublishingSet,
@@ -122,8 +123,7 @@
122 # XXX cprov 2006-08-18: move it away, perhaps archivepublisher/pool.py123 # XXX cprov 2006-08-18: move it away, perhaps archivepublisher/pool.py
123 """Return the pool path for a given source name and component name."""124 """Return the pool path for a given source name and component name."""
124 from lp.archivepublisher.diskpool import poolify125 from lp.archivepublisher.diskpool import poolify
125 return os.path.join(126 return os.path.join('pool', poolify(source_name, component_name))
126 'pool', poolify(source_name, component_name))
127127
128128
129def get_component(archive, distroseries, component):129def get_component(archive, distroseries, component):
@@ -160,8 +160,7 @@
160160
161def proxied_urls(files, parent):161def proxied_urls(files, parent):
162 """Run the files passed through `ProxiedLibraryFileAlias`."""162 """Run the files passed through `ProxiedLibraryFileAlias`."""
163 return [163 return [ProxiedLibraryFileAlias(file, parent).http_url for file in files]
164 ProxiedLibraryFileAlias(file, parent).http_url for file in files]
165164
166165
167class FilePublishingBase:166class FilePublishingBase:
@@ -178,15 +177,13 @@
178 sha1 = filealias.content.sha1177 sha1 = filealias.content.sha1
179 path = diskpool.pathFor(component, source, filename)178 path = diskpool.pathFor(component, source, filename)
180179
181 action = diskpool.addFile(180 action = diskpool.addFile(component, source, filename, sha1, filealias)
182 component, source, filename, sha1, filealias)
183 if action == diskpool.results.FILE_ADDED:181 if action == diskpool.results.FILE_ADDED:
184 log.debug("Added %s from library" % path)182 log.debug("Added %s from library" % path)
185 elif action == diskpool.results.SYMLINK_ADDED:183 elif action == diskpool.results.SYMLINK_ADDED:
186 log.debug("%s created as a symlink." % path)184 log.debug("%s created as a symlink." % path)
187 elif action == diskpool.results.NONE:185 elif action == diskpool.results.NONE:
188 log.debug(186 log.debug("%s is already in pool with the same content." % path)
189 "%s is already in pool with the same content." % path)
190187
191 @property188 @property
192 def archive_url(self):189 def archive_url(self):
@@ -435,8 +432,7 @@
435 foreignKey='SourcePackageName', dbName='sourcepackagename')432 foreignKey='SourcePackageName', dbName='sourcepackagename')
436 sourcepackagerelease = ForeignKey(433 sourcepackagerelease = ForeignKey(
437 foreignKey='SourcePackageRelease', dbName='sourcepackagerelease')434 foreignKey='SourcePackageRelease', dbName='sourcepackagerelease')
438 distroseries = ForeignKey(435 distroseries = ForeignKey(foreignKey='DistroSeries', dbName='distroseries')
439 foreignKey='DistroSeries', dbName='distroseries')
440 component = ForeignKey(foreignKey='Component', dbName='component')436 component = ForeignKey(foreignKey='Component', dbName='component')
441 section = ForeignKey(foreignKey='Section', dbName='section')437 section = ForeignKey(foreignKey='Section', dbName='section')
442 status = EnumCol(schema=PackagePublishingStatus)438 status = EnumCol(schema=PackagePublishingStatus)
@@ -732,8 +728,7 @@
732 """See `IPublishing`."""728 """See `IPublishing`."""
733 release = self.sourcepackagerelease729 release = self.sourcepackagerelease
734 name = release.sourcepackagename.name730 name = release.sourcepackagename.name
735 return "%s %s in %s" % (name, release.version,731 return "%s %s in %s" % (name, release.version, self.distroseries.name)
736 self.distroseries.name)
737732
738 def buildIndexStanzaFields(self):733 def buildIndexStanzaFields(self):
739 """See `IPublishing`."""734 """See `IPublishing`."""
@@ -925,6 +920,11 @@
925920
926 def requestDeletion(self, removed_by, removal_comment=None):921 def requestDeletion(self, removed_by, removal_comment=None):
927 """See `IPublishing`."""922 """See `IPublishing`."""
923 if not self.archive.canModifySuite(self.distroseries, self.pocket):
924 raise DeletionError(
925 "Cannot delete publications from suite '%s'" %
926 self.distroseries.getSuite(self.pocket))
927
928 self.setDeleted(removed_by, removal_comment)928 self.setDeleted(removed_by, removal_comment)
929 if self.archive.is_main:929 if self.archive.is_main:
930 dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)930 dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)
@@ -1053,8 +1053,7 @@
1053 # ...1053 # ...
1054 # <DESCRIPTION LN>1054 # <DESCRIPTION LN>
1055 descr_lines = [line.lstrip() for line in bpr.description.splitlines()]1055 descr_lines = [line.lstrip() for line in bpr.description.splitlines()]
1056 bin_description = (1056 bin_description = '%s\n %s' % (bpr.summary, '\n '.join(descr_lines))
1057 '%s\n %s' % (bpr.summary, '\n '.join(descr_lines)))
10581057
1059 # Dealing with architecturespecific field.1058 # Dealing with architecturespecific field.
1060 # Present 'all' in every archive index for architecture1059 # Present 'all' in every archive index for architecture
@@ -1329,11 +1328,9 @@
1329 ]1328 ]
13301329
1331 if start_date is not None:1330 if start_date is not None:
1332 clauses.append(1331 clauses.append(BinaryPackageReleaseDownloadCount.day >= start_date)
1333 BinaryPackageReleaseDownloadCount.day >= start_date)
1334 if end_date is not None:1332 if end_date is not None:
1335 clauses.append(1333 clauses.append(BinaryPackageReleaseDownloadCount.day <= end_date)
1336 BinaryPackageReleaseDownloadCount.day <= end_date)
13371334
1338 return clauses1335 return clauses
13391336
@@ -1373,6 +1370,11 @@
13731370
1374 def requestDeletion(self, removed_by, removal_comment=None):1371 def requestDeletion(self, removed_by, removal_comment=None):
1375 """See `IPublishing`."""1372 """See `IPublishing`."""
1373 if not self.archive.canModifySuite(self.distroseries, self.pocket):
1374 raise DeletionError(
1375 "Cannot delete publications from suite '%s'" %
1376 self.distroseries.getSuite(self.pocket))
1377
1376 self.setDeleted(removed_by, removal_comment)1378 self.setDeleted(removed_by, removal_comment)
13771379
1378 def binaryFileUrls(self, include_meta=False):1380 def binaryFileUrls(self, include_meta=False):
@@ -1415,8 +1417,7 @@
1415 # Find the DAS in this series corresponding to the original1417 # Find the DAS in this series corresponding to the original
1416 # build arch tag. If it does not exist or is disabled, we should1418 # build arch tag. If it does not exist or is disabled, we should
1417 # not publish.1419 # not publish.
1418 target_arch = arch_map.get(1420 target_arch = arch_map.get(bpr.build.arch_tag)
1419 bpr.build.distro_arch_series.architecturetag)
1420 target_archs = [target_arch] if target_arch is not None else []1421 target_archs = [target_arch] if target_arch is not None else []
1421 else:1422 else:
1422 target_archs = archs1423 target_archs = archs
@@ -1941,8 +1942,7 @@
1941 # separate query for now.1942 # separate query for now.
1942 source_pubs.update(store.find(1943 source_pubs.update(store.find(
1943 SourcePackagePublishingHistory,1944 SourcePackagePublishingHistory,
1944 SourcePackagePublishingHistory.id.is_in(1945 SourcePackagePublishingHistory.id.is_in(pubs_without_builds),
1945 pubs_without_builds),
1946 SourcePackagePublishingHistory.archive == archive))1946 SourcePackagePublishingHistory.archive == archive))
1947 # For each source_pub found, provide an aggregate summary of its1947 # For each source_pub found, provide an aggregate summary of its
1948 # builds.1948 # builds.
@@ -2043,6 +2043,14 @@
2043 return2043 return
2044 assert len(sources) + len(binaries) == len(pubs)2044 assert len(sources) + len(binaries) == len(pubs)
20452045
2046 locations = set(
2047 (pub.archive, pub.distroseries, pub.pocket) for pub in pubs)
2048 for archive, distroseries, pocket in locations:
2049 if not archive.canModifySuite(distroseries, pocket):
2050 raise DeletionError(
2051 "Cannot delete publications from suite '%s'" %
2052 distroseries.getSuite(pocket))
2053
2046 spph_ids = [spph.id for spph in sources]2054 spph_ids = [spph.id for spph in sources]
2047 self.setMultipleDeleted(2055 self.setMultipleDeleted(
2048 SourcePackagePublishingHistory, spph_ids, removed_by,2056 SourcePackagePublishingHistory, spph_ids, removed_by,
20492057
=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
--- lib/lp/soyuz/tests/test_publishing.py 2013-02-20 12:28:38 +0000
+++ lib/lp/soyuz/tests/test_publishing.py 2013-03-08 22:44:24 +0000
@@ -42,6 +42,7 @@
42from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet42from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
43from lp.soyuz.interfaces.component import IComponentSet43from lp.soyuz.interfaces.component import IComponentSet
44from lp.soyuz.interfaces.publishing import (44from lp.soyuz.interfaces.publishing import (
45 DeletionError,
45 IPublishingSet,46 IPublishingSet,
46 OverrideError,47 OverrideError,
47 PackagePublishingPriority,48 PackagePublishingPriority,
@@ -504,11 +505,8 @@
504 if new_version is None:505 if new_version is None:
505 new_version = version506 new_version = version
506 changesfile_content = ''507 changesfile_content = ''
507 handle = open(changesfile_path, 'r')508 with open(changesfile_path, 'r') as handle:
508 try:
509 changesfile_content = handle.read()509 changesfile_content = handle.read()
510 finally:
511 handle.close()
512510
513 source = self.getPubSource(511 source = self.getPubSource(
514 sourcename=sourcename, archive=archive, version=new_version,512 sourcename=sourcename, archive=archive, version=new_version,
@@ -648,9 +646,9 @@
648 dominant = supersededby.binarypackagerelease.build646 dominant = supersededby.binarypackagerelease.build
649 else:647 else:
650 dominant = supersededby.sourcepackagerelease648 dominant = supersededby.sourcepackagerelease
651 self.assertEquals(dominant, pub.supersededby)649 self.assertEqual(dominant, pub.supersededby)
652 else:650 else:
653 self.assertIs(None, pub.supersededby)651 self.assertIsNone(pub.supersededby)
654652
655653
656class TestNativePublishing(TestNativePublishingBase):654class TestNativePublishing(TestNativePublishingBase):
@@ -660,9 +658,7 @@
660 # the corresponding files are dumped in the disk pool/.658 # the corresponding files are dumped in the disk pool/.
661 pub_source = self.getPubSource(filecontent='Hello world')659 pub_source = self.getPubSource(filecontent='Hello world')
662 pub_source.publish(self.disk_pool, self.logger)660 pub_source.publish(self.disk_pool, self.logger)
663 self.assertEqual(661 self.assertEqual(PackagePublishingStatus.PUBLISHED, pub_source.status)
664 PackagePublishingStatus.PUBLISHED,
665 pub_source.status)
666 pool_path = "%s/main/f/foo/foo_666.dsc" % self.pool_dir662 pool_path = "%s/main/f/foo/foo_666.dsc" % self.pool_dir
667 self.assertEqual(open(pool_path).read().strip(), 'Hello world')663 self.assertEqual(open(pool_path).read().strip(), 'Hello world')
668664
@@ -671,9 +667,7 @@
671 # the corresponding files are dumped in the disk pool/.667 # the corresponding files are dumped in the disk pool/.
672 pub_binary = self.getPubBinaries(filecontent='Hello world')[0]668 pub_binary = self.getPubBinaries(filecontent='Hello world')[0]
673 pub_binary.publish(self.disk_pool, self.logger)669 pub_binary.publish(self.disk_pool, self.logger)
674 self.assertEqual(670 self.assertEqual(PackagePublishingStatus.PUBLISHED, pub_binary.status)
675 PackagePublishingStatus.PUBLISHED,
676 pub_binary.status)
677 pool_path = "%s/main/f/foo/foo-bin_666_all.deb" % self.pool_dir671 pool_path = "%s/main/f/foo/foo-bin_666_all.deb" % self.pool_dir
678 self.assertEqual(open(pool_path).read().strip(), 'Hello world')672 self.assertEqual(open(pool_path).read().strip(), 'Hello world')
679673
@@ -688,9 +682,8 @@
688 foo_path = os.path.join(self.pool_dir, 'main', 'f', 'foo')682 foo_path = os.path.join(self.pool_dir, 'main', 'f', 'foo')
689 os.makedirs(foo_path)683 os.makedirs(foo_path)
690 foo_dsc_path = os.path.join(foo_path, 'foo_666.dsc')684 foo_dsc_path = os.path.join(foo_path, 'foo_666.dsc')
691 foo_dsc = open(foo_dsc_path, 'w')685 with open(foo_dsc_path, 'w') as foo_dsc:
692 foo_dsc.write('Hello world')686 foo_dsc.write('Hello world')
693 foo_dsc.close()
694687
695 pub_source = self.getPubSource(filecontent="Something")688 pub_source = self.getPubSource(filecontent="Something")
696 pub_source.publish(self.disk_pool, self.logger)689 pub_source.publish(self.disk_pool, self.logger)
@@ -699,8 +692,7 @@
699 self.assertEqual("PoolFileOverwriteError", self.oopses[0]['type'])692 self.assertEqual("PoolFileOverwriteError", self.oopses[0]['type'])
700693
701 self.layer.commit()694 self.layer.commit()
702 self.assertEqual(695 self.assertEqual(pub_source.status, PackagePublishingStatus.PENDING)
703 pub_source.status, PackagePublishingStatus.PENDING)
704 self.assertEqual(open(foo_dsc_path).read().strip(), 'Hello world')696 self.assertEqual(open(foo_dsc_path).read().strip(), 'Hello world')
705697
706 def testPublishingDifferentContents(self):698 def testPublishingDifferentContents(self):
@@ -711,8 +703,7 @@
711703
712 foo_name = "%s/main/f/foo/foo_666.dsc" % self.pool_dir704 foo_name = "%s/main/f/foo/foo_666.dsc" % self.pool_dir
713 pub_source.sync()705 pub_source.sync()
714 self.assertEqual(706 self.assertEqual(pub_source.status, PackagePublishingStatus.PUBLISHED)
715 pub_source.status, PackagePublishingStatus.PUBLISHED)
716 self.assertEqual(open(foo_name).read().strip(), 'foo is happy')707 self.assertEqual(open(foo_name).read().strip(), 'foo is happy')
717708
718 # try to publish 'foo' again with a different content, it709 # try to publish 'foo' again with a different content, it
@@ -723,8 +714,7 @@
723 self.layer.commit()714 self.layer.commit()
724715
725 pub_source2.sync()716 pub_source2.sync()
726 self.assertEqual(717 self.assertEqual(pub_source2.status, PackagePublishingStatus.PENDING)
727 pub_source2.status, PackagePublishingStatus.PENDING)
728 self.assertEqual(open(foo_name).read().strip(), 'foo is happy')718 self.assertEqual(open(foo_name).read().strip(), 'foo is happy')
729719
730 def testPublishingAlreadyInPool(self):720 def testPublishingAlreadyInPool(self):
@@ -740,16 +730,14 @@
740 bar_name = "%s/main/b/bar/bar_666.dsc" % self.pool_dir730 bar_name = "%s/main/b/bar/bar_666.dsc" % self.pool_dir
741 self.assertEqual(open(bar_name).read().strip(), 'bar is good')731 self.assertEqual(open(bar_name).read().strip(), 'bar is good')
742 pub_source.sync()732 pub_source.sync()
743 self.assertEqual(733 self.assertEqual(pub_source.status, PackagePublishingStatus.PUBLISHED)
744 pub_source.status, PackagePublishingStatus.PUBLISHED)
745734
746 pub_source2 = self.getPubSource(735 pub_source2 = self.getPubSource(
747 sourcename='bar', filecontent='bar is good')736 sourcename='bar', filecontent='bar is good')
748 pub_source2.publish(self.disk_pool, self.logger)737 pub_source2.publish(self.disk_pool, self.logger)
749 self.layer.commit()738 self.layer.commit()
750 pub_source2.sync()739 pub_source2.sync()
751 self.assertEqual(740 self.assertEqual(pub_source2.status, PackagePublishingStatus.PUBLISHED)
752 pub_source2.status, PackagePublishingStatus.PUBLISHED)
753741
754 def testPublishingSymlink(self):742 def testPublishingSymlink(self):
755 """Test if publishOne moving publication between components.743 """Test if publishOne moving publication between components.
@@ -759,8 +747,7 @@
759 """747 """
760 content = 'am I a file or a symbolic link ?'748 content = 'am I a file or a symbolic link ?'
761 # publish sim.dsc in main and re-publish in universe749 # publish sim.dsc in main and re-publish in universe
762 pub_source = self.getPubSource(750 pub_source = self.getPubSource(sourcename='sim', filecontent=content)
763 sourcename='sim', filecontent=content)
764 pub_source2 = self.getPubSource(751 pub_source2 = self.getPubSource(
765 sourcename='sim', component='universe', filecontent=content)752 sourcename='sim', component='universe', filecontent=content)
766 pub_source.publish(self.disk_pool, self.logger)753 pub_source.publish(self.disk_pool, self.logger)
@@ -769,10 +756,8 @@
769756
770 pub_source.sync()757 pub_source.sync()
771 pub_source2.sync()758 pub_source2.sync()
772 self.assertEqual(759 self.assertEqual(pub_source.status, PackagePublishingStatus.PUBLISHED)
773 pub_source.status, PackagePublishingStatus.PUBLISHED)760 self.assertEqual(pub_source2.status, PackagePublishingStatus.PUBLISHED)
774 self.assertEqual(
775 pub_source2.status, PackagePublishingStatus.PUBLISHED)
776761
777 # check the resulted symbolic link762 # check the resulted symbolic link
778 sim_universe = "%s/universe/s/sim/sim_666.dsc" % self.pool_dir763 sim_universe = "%s/universe/s/sim/sim_666.dsc" % self.pool_dir
@@ -788,8 +773,7 @@
788 self.layer.commit()773 self.layer.commit()
789774
790 pub_source3.sync()775 pub_source3.sync()
791 self.assertEqual(776 self.assertEqual(pub_source3.status, PackagePublishingStatus.PENDING)
792 pub_source3.status, PackagePublishingStatus.PENDING)
793777
794 def testPublishInAnotherArchive(self):778 def testPublishInAnotherArchive(self):
795 """Publication in another archive779 """Publication in another archive
@@ -882,10 +866,10 @@
882 copies = (copied, )866 copies = (copied, )
883867
884 for copy in copies:868 for copy in copies:
885 self.assertEquals(copy.component, pub_record.component)869 self.assertEqual(pub_record.component, copy.component)
886 copy.overrideFromAncestry()870 copy.overrideFromAncestry()
887 self.layer.commit()871 self.layer.commit()
888 self.assertEquals(copy.component.name, 'universe')872 self.assertEqual("universe", copy.component.name)
889873
890 def test_overrideFromAncestry_fallback_to_source_component(self):874 def test_overrideFromAncestry_fallback_to_source_component(self):
891 # overrideFromAncestry on the lack of ancestry, falls back to the875 # overrideFromAncestry on the lack of ancestry, falls back to the
@@ -966,7 +950,7 @@
966 spph = self.factory.makeSourcePackagePublishingHistory(950 spph = self.factory.makeSourcePackagePublishingHistory(
967 sourcepackagerelease=spr, archive=ppa)951 sourcepackagerelease=spr, archive=ppa)
968 spph.overrideFromAncestry()952 spph.overrideFromAncestry()
969 self.assertEquals(spph.component.name, 'main')953 self.assertEqual("main", spph.component.name)
970954
971 def test_ppa_override_with_ancestry(self):955 def test_ppa_override_with_ancestry(self):
972 # Test a PPA publication with ancestry956 # Test a PPA publication with ancestry
@@ -978,7 +962,7 @@
978 spph2 = self.factory.makeSourcePackagePublishingHistory(962 spph2 = self.factory.makeSourcePackagePublishingHistory(
979 sourcepackagerelease=spr, archive=ppa)963 sourcepackagerelease=spr, archive=ppa)
980 spph2.overrideFromAncestry()964 spph2.overrideFromAncestry()
981 self.assertEquals(spph2.component.name, 'main')965 self.assertEqual("main", spph2.component.name)
982966
983 def test_copyTo_with_overrides(self):967 def test_copyTo_with_overrides(self):
984 # Specifying overrides with copyTo should result in the new968 # Specifying overrides with copyTo should result in the new
@@ -1009,8 +993,7 @@
1009 # SPPH's ancestor get's populated when a spph is copied over.993 # SPPH's ancestor get's populated when a spph is copied over.
1010 target_archive = self.factory.makeArchive()994 target_archive = self.factory.makeArchive()
1011 spph = self.factory.makeSourcePackagePublishingHistory()995 spph = self.factory.makeSourcePackagePublishingHistory()
1012 copy = spph.copyTo(996 copy = spph.copyTo(spph.distroseries, spph.pocket, target_archive)
1013 spph.distroseries, spph.pocket, target_archive)
1014997
1015 self.assertEqual(spph, copy.ancestor)998 self.assertEqual(spph, copy.ancestor)
1016999
@@ -1059,7 +1042,8 @@
1059 """1042 """
1060 available_archs = [self.sparc_distroarch, self.avr_distroarch]1043 available_archs = [self.sparc_distroarch, self.avr_distroarch]
1061 pubrec = self.getPubSource(architecturehintlist='any')1044 pubrec = self.getPubSource(architecturehintlist='any')
1062 self.assertEquals([self.sparc_distroarch],1045 self.assertEqual(
1046 [self.sparc_distroarch],
1063 pubrec._getAllowedArchitectures(available_archs))1047 pubrec._getAllowedArchitectures(available_archs))
10641048
1065 def test__getAllowedArchitectures_restricted_override(self):1049 def test__getAllowedArchitectures_restricted_override(self):
@@ -1071,7 +1055,8 @@
1071 available_archs = [self.sparc_distroarch, self.avr_distroarch]1055 available_archs = [self.sparc_distroarch, self.avr_distroarch]
1072 getUtility(IArchiveArchSet).new(self.archive, self.avr_family)1056 getUtility(IArchiveArchSet).new(self.archive, self.avr_family)
1073 pubrec = self.getPubSource(architecturehintlist='any')1057 pubrec = self.getPubSource(architecturehintlist='any')
1074 self.assertEquals([self.sparc_distroarch, self.avr_distroarch],1058 self.assertEqual(
1059 [self.sparc_distroarch, self.avr_distroarch],
1075 pubrec._getAllowedArchitectures(available_archs))1060 pubrec._getAllowedArchitectures(available_archs))
10761061
1077 def test_createMissingBuilds_restricts_any(self):1062 def test_createMissingBuilds_restricts_any(self):
@@ -1080,8 +1065,8 @@
1080 """1065 """
1081 pubrec = self.getPubSource(architecturehintlist='any')1066 pubrec = self.getPubSource(architecturehintlist='any')
1082 builds = pubrec.createMissingBuilds()1067 builds = pubrec.createMissingBuilds()
1083 self.assertEquals(1, len(builds))1068 self.assertEqual(1, len(builds))
1084 self.assertEquals(self.sparc_distroarch, builds[0].distro_arch_series)1069 self.assertEqual(self.sparc_distroarch, builds[0].distro_arch_series)
10851070
1086 def test_createMissingBuilds_restricts_explicitlist(self):1071 def test_createMissingBuilds_restricts_explicitlist(self):
1087 """createMissingBuilds() limits builds targeted at a variety of1072 """createMissingBuilds() limits builds targeted at a variety of
@@ -1089,8 +1074,8 @@
1089 """1074 """
1090 pubrec = self.getPubSource(architecturehintlist='sparc i386 avr')1075 pubrec = self.getPubSource(architecturehintlist='sparc i386 avr')
1091 builds = pubrec.createMissingBuilds()1076 builds = pubrec.createMissingBuilds()
1092 self.assertEquals(1, len(builds))1077 self.assertEqual(1, len(builds))
1093 self.assertEquals(self.sparc_distroarch, builds[0].distro_arch_series)1078 self.assertEqual(self.sparc_distroarch, builds[0].distro_arch_series)
10941079
1095 def test_createMissingBuilds_restricts_all(self):1080 def test_createMissingBuilds_restricts_all(self):
1096 """createMissingBuilds() should limit builds targeted at 'all'1081 """createMissingBuilds() should limit builds targeted at 'all'
@@ -1099,8 +1084,8 @@
1099 """1084 """
1100 pubrec = self.getPubSource(architecturehintlist='all')1085 pubrec = self.getPubSource(architecturehintlist='all')
1101 builds = pubrec.createMissingBuilds()1086 builds = pubrec.createMissingBuilds()
1102 self.assertEquals(1, len(builds))1087 self.assertEqual(1, len(builds))
1103 self.assertEquals(self.sparc_distroarch, builds[0].distro_arch_series)1088 self.assertEqual(self.sparc_distroarch, builds[0].distro_arch_series)
11041089
1105 def test_createMissingBuilds_restrict_override(self):1090 def test_createMissingBuilds_restrict_override(self):
1106 """createMissingBuilds() should limit builds targeted at 'any'1091 """createMissingBuilds() should limit builds targeted at 'any'
@@ -1110,9 +1095,9 @@
1110 getUtility(IArchiveArchSet).new(self.archive, self.avr_family)1095 getUtility(IArchiveArchSet).new(self.archive, self.avr_family)
1111 pubrec = self.getPubSource(architecturehintlist='any')1096 pubrec = self.getPubSource(architecturehintlist='any')
1112 builds = pubrec.createMissingBuilds()1097 builds = pubrec.createMissingBuilds()
1113 self.assertEquals(2, len(builds))1098 self.assertEqual(2, len(builds))
1114 self.assertEquals(self.avr_distroarch, builds[0].distro_arch_series)1099 self.assertEqual(self.avr_distroarch, builds[0].distro_arch_series)
1115 self.assertEquals(self.sparc_distroarch, builds[1].distro_arch_series)1100 self.assertEqual(self.sparc_distroarch, builds[1].distro_arch_series)
11161101
11171102
1118class PublishingSetTests(TestCaseWithFactory):1103class PublishingSetTests(TestCaseWithFactory):
@@ -1175,59 +1160,69 @@
11751160
1176 layer = ZopelessDatabaseLayer1161 layer = ZopelessDatabaseLayer
11771162
1163 def setUp(self):
1164 super(TestPublishingSetLite, self).setUp()
1165 self.person = self.factory.makePerson()
1166
1178 def test_requestDeletion_marks_SPPHs_deleted(self):1167 def test_requestDeletion_marks_SPPHs_deleted(self):
1179 spph = self.factory.makeSourcePackagePublishingHistory()1168 spph = self.factory.makeSourcePackagePublishingHistory()
1180 getUtility(IPublishingSet).requestDeletion(1169 getUtility(IPublishingSet).requestDeletion([spph], self.person)
1181 [spph], self.factory.makePerson())
1182 self.assertEqual(PackagePublishingStatus.DELETED, spph.status)1170 self.assertEqual(PackagePublishingStatus.DELETED, spph.status)
11831171
1184 def test_requestDeletion_leaves_other_SPPHs_alone(self):1172 def test_requestDeletion_leaves_other_SPPHs_alone(self):
1185 spph = self.factory.makeSourcePackagePublishingHistory()1173 spph = self.factory.makeSourcePackagePublishingHistory()
1186 other_spph = self.factory.makeSourcePackagePublishingHistory()1174 other_spph = self.factory.makeSourcePackagePublishingHistory()
1187 getUtility(IPublishingSet).requestDeletion(1175 getUtility(IPublishingSet).requestDeletion([other_spph], self.person)
1188 [other_spph], self.factory.makePerson())
1189 self.assertEqual(PackagePublishingStatus.PENDING, spph.status)1176 self.assertEqual(PackagePublishingStatus.PENDING, spph.status)
11901177
1191 def test_requestDeletion_marks_BPPHs_deleted(self):1178 def test_requestDeletion_marks_BPPHs_deleted(self):
1192 bpph = self.factory.makeBinaryPackagePublishingHistory()1179 bpph = self.factory.makeBinaryPackagePublishingHistory()
1193 getUtility(IPublishingSet).requestDeletion(1180 getUtility(IPublishingSet).requestDeletion([bpph], self.person)
1194 [bpph], self.factory.makePerson())
1195 self.assertEqual(PackagePublishingStatus.DELETED, bpph.status)1181 self.assertEqual(PackagePublishingStatus.DELETED, bpph.status)
11961182
1197 def test_requestDeletion_marks_attached_BPPHs_deleted(self):1183 def test_requestDeletion_marks_attached_BPPHs_deleted(self):
1198 bpph = self.factory.makeBinaryPackagePublishingHistory()1184 bpph = self.factory.makeBinaryPackagePublishingHistory()
1199 spph = self.factory.makeSPPHForBPPH(bpph)1185 spph = self.factory.makeSPPHForBPPH(bpph)
1200 getUtility(IPublishingSet).requestDeletion(1186 getUtility(IPublishingSet).requestDeletion([spph], self.person)
1201 [spph], self.factory.makePerson())
1202 self.assertEqual(PackagePublishingStatus.DELETED, spph.status)1187 self.assertEqual(PackagePublishingStatus.DELETED, spph.status)
12031188
1204 def test_requestDeletion_leaves_other_BPPHs_alone(self):1189 def test_requestDeletion_leaves_other_BPPHs_alone(self):
1205 bpph = self.factory.makeBinaryPackagePublishingHistory()1190 bpph = self.factory.makeBinaryPackagePublishingHistory()
1206 unrelated_spph = self.factory.makeSourcePackagePublishingHistory()1191 unrelated_spph = self.factory.makeSourcePackagePublishingHistory()
1207 getUtility(IPublishingSet).requestDeletion(1192 getUtility(IPublishingSet).requestDeletion(
1208 [unrelated_spph], self.factory.makePerson())1193 [unrelated_spph], self.person)
1209 self.assertEqual(PackagePublishingStatus.PENDING, bpph.status)1194 self.assertEqual(PackagePublishingStatus.PENDING, bpph.status)
12101195
1211 def test_requestDeletion_accepts_empty_sources_list(self):1196 def test_requestDeletion_accepts_empty_sources_list(self):
1212 person = self.factory.makePerson()1197 getUtility(IPublishingSet).requestDeletion([], self.person)
1213 getUtility(IPublishingSet).requestDeletion([], person)
1214 # The test is that this does not fail.1198 # The test is that this does not fail.
1215 Store.of(person).flush()1199 Store.of(self.person).flush()
12161200
1217 def test_requestDeletion_creates_DistroSeriesDifferenceJobs(self):1201 def test_requestDeletion_creates_DistroSeriesDifferenceJobs(self):
1218 dsp = self.factory.makeDistroSeriesParent()1202 dsp = self.factory.makeDistroSeriesParent()
1219 series = dsp.derived_series
1220 spph = self.factory.makeSourcePackagePublishingHistory(1203 spph = self.factory.makeSourcePackagePublishingHistory(
1221 series, pocket=PackagePublishingPocket.RELEASE)1204 dsp.derived_series, pocket=PackagePublishingPocket.RELEASE)
1222 spn = spph.sourcepackagerelease.sourcepackagename1205 spn = spph.sourcepackagerelease.sourcepackagename
12231206 getUtility(IPublishingSet).requestDeletion([spph], self.person)
1224 getUtility(IPublishingSet).requestDeletion(
1225 [spph], self.factory.makePerson())
1226
1227 self.assertEqual(1207 self.assertEqual(
1228 1, len(find_waiting_jobs(1208 1, len(find_waiting_jobs(
1229 dsp.derived_series, spn, dsp.parent_series)))1209 dsp.derived_series, spn, dsp.parent_series)))
12301210
1211 def test_requestDeletion_disallows_unmodifiable_suites(self):
1212 bpph = self.factory.makeBinaryPackagePublishingHistory(
1213 pocket=PackagePublishingPocket.RELEASE)
1214 spph = self.factory.makeSourcePackagePublishingHistory(
1215 distroseries=bpph.distroseries,
1216 pocket=PackagePublishingPocket.RELEASE)
1217 spph.distroseries.status = SeriesStatus.CURRENT
1218 message = "Cannot delete publications from suite '%s'" % (
1219 spph.distroseries.getSuite(spph.pocket))
1220 for pub in spph, bpph:
1221 self.assertRaisesWithContent(
1222 DeletionError, message, pub.requestDeletion, self.person)
1223 self.assertRaisesWithContent(
1224 DeletionError, message, pub.api_requestDeletion, self.person)
1225
12311226
1232class TestSourceDomination(TestNativePublishingBase):1227class TestSourceDomination(TestNativePublishingBase):
1233 """Test SourcePackagePublishingHistory.supersede() operates correctly."""1228 """Test SourcePackagePublishingHistory.supersede() operates correctly."""
@@ -1264,7 +1259,7 @@
12641259
1265 self.assertRaises(AssertionError, source.supersede, super_source)1260 self.assertRaises(AssertionError, source.supersede, super_source)
1266 self.checkSuperseded([source], super_source)1261 self.checkSuperseded([source], super_source)
1267 self.assertEquals(super_date, source.datesuperseded)1262 self.assertEqual(super_date, source.datesuperseded)
12681263
12691264
1270class TestBinaryDomination(TestNativePublishingBase):1265class TestBinaryDomination(TestNativePublishingBase):
@@ -1343,7 +1338,7 @@
13431338
1344 self.assertRaises(AssertionError, bin.supersede, super_bin)1339 self.assertRaises(AssertionError, bin.supersede, super_bin)
1345 self.checkSuperseded([bin], super_bin)1340 self.checkSuperseded([bin], super_bin)
1346 self.assertEquals(super_date, bin.datesuperseded)1341 self.assertEqual(super_date, bin.datesuperseded)
13471342
1348 def testSkipsSupersededArchIndependentBinary(self):1343 def testSkipsSupersededArchIndependentBinary(self):
1349 """Check that supersede() skips a superseded arch-indep binary.1344 """Check that supersede() skips a superseded arch-indep binary.
@@ -1365,7 +1360,7 @@
13651360
1366 bin.supersede(super_bin)1361 bin.supersede(super_bin)
1367 self.checkSuperseded([bin], super_bin)1362 self.checkSuperseded([bin], super_bin)
1368 self.assertEquals(super_date, bin.datesuperseded)1363 self.assertEqual(super_date, bin.datesuperseded)
13691364
1370 def testSupersedesCorrespondingDDEB(self):1365 def testSupersedesCorrespondingDDEB(self):
1371 """Check that supersede() takes with it any corresponding DDEB.1366 """Check that supersede() takes with it any corresponding DDEB.
@@ -1379,8 +1374,7 @@
13791374
1380 # Each of these will return (i386 deb, i386 ddeb, hppa deb,1375 # Each of these will return (i386 deb, i386 ddeb, hppa deb,
1381 # hppa ddeb).1376 # hppa ddeb).
1382 bins = self.getPubBinaries(1377 bins = self.getPubBinaries(architecturespecific=True, with_debug=True)
1383 architecturespecific=True, with_debug=True)
1384 super_bins = self.getPubBinaries(1378 super_bins = self.getPubBinaries(
1385 architecturespecific=True, with_debug=True)1379 architecturespecific=True, with_debug=True)
13861380
@@ -1405,8 +1399,7 @@
1405 distribution=self.ubuntutest)1399 distribution=self.ubuntutest)
14061400
1407 # This will return (i386 deb, i386 ddeb, hppa deb, hppa ddeb).1401 # This will return (i386 deb, i386 ddeb, hppa deb, hppa ddeb).
1408 bins = self.getPubBinaries(1402 bins = self.getPubBinaries(architecturespecific=True, with_debug=True)
1409 architecturespecific=True, with_debug=True)
1410 self.assertRaises(AssertionError, bins[0].supersede, bins[1])1403 self.assertRaises(AssertionError, bins[0].supersede, bins[1])
14111404
14121405
@@ -1414,9 +1407,8 @@
1414 """Test BinaryPackagePublishingHistory._getOtherPublications() works."""1407 """Test BinaryPackagePublishingHistory._getOtherPublications() works."""
14151408
1416 def checkOtherPublications(self, this, others):1409 def checkOtherPublications(self, this, others):
1417 self.assertEquals(1410 self.assertContentEqual(
1418 set(removeSecurityProxy(this)._getOtherPublications()),1411 removeSecurityProxy(this)._getOtherPublications(), others)
1419 set(others))
14201412
1421 def testFindsOtherArchIndepPublications(self):1413 def testFindsOtherArchIndepPublications(self):
1422 """Arch-indep publications with the same overrides should be found."""1414 """Arch-indep publications with the same overrides should be found."""
@@ -1529,7 +1521,7 @@
1529 # SPPH, BPPHs and BPRs.1521 # SPPH, BPPHs and BPRs.
1530 with StormStatementRecorder() as recorder:1522 with StormStatementRecorder() as recorder:
1531 bins = spph.getBuiltBinaries()1523 bins = spph.getBuiltBinaries()
1532 self.assertEquals(0, len(bins))1524 self.assertEqual(0, len(bins))
1533 self.assertThat(recorder, HasQueryCount(Equals(3)))1525 self.assertThat(recorder, HasQueryCount(Equals(3)))
15341526
1535 self.getPubBinaries(pub_source=spph)1527 self.getPubBinaries(pub_source=spph)
@@ -1541,7 +1533,7 @@
1541 # BPF has no query penalty.1533 # BPF has no query penalty.
1542 with StormStatementRecorder() as recorder:1534 with StormStatementRecorder() as recorder:
1543 bins = spph.getBuiltBinaries(want_files=True)1535 bins = spph.getBuiltBinaries(want_files=True)
1544 self.assertEquals(2, len(bins))1536 self.assertEqual(2, len(bins))
1545 for bpph in bins:1537 for bpph in bins:
1546 files = bpph.binarypackagerelease.files1538 files = bpph.binarypackagerelease.files
1547 self.assertEqual(1, len(files))1539 self.assertEqual(1, len(files))
@@ -1595,8 +1587,7 @@
1595 def test_architecture_independent(self):1587 def test_architecture_independent(self):
1596 # Architecture-independent binaries get published to all enabled1588 # Architecture-independent binaries get published to all enabled
1597 # DASes in the series.1589 # DASes in the series.
1598 bpr = self.factory.makeBinaryPackageRelease(1590 bpr = self.factory.makeBinaryPackageRelease(architecturespecific=False)
1599 architecturespecific=False)
1600 # Create 3 architectures. The binary will not be published in1591 # Create 3 architectures. The binary will not be published in
1601 # the disabled one.1592 # the disabled one.
1602 target_das_a = self.factory.makeDistroArchSeries()1593 target_das_a = self.factory.makeDistroArchSeries()
@@ -1606,12 +1597,11 @@
1606 self.factory.makeDistroArchSeries(1597 self.factory.makeDistroArchSeries(
1607 distroseries=target_das_a.distroseries, enabled=False)1598 distroseries=target_das_a.distroseries, enabled=False)
1608 args = self.makeArgs([bpr], target_das_a.distroseries)1599 args = self.makeArgs([bpr], target_das_a.distroseries)
1609 bpphs = getUtility(IPublishingSet).publishBinaries(1600 bpphs = getUtility(IPublishingSet).publishBinaries(**args)
1610 **args)1601 self.assertEqual(2, len(bpphs))
1611 self.assertEquals(2, len(bpphs))1602 self.assertContentEqual(
1612 self.assertEquals(1603 (target_das_a, target_das_b),
1613 set((target_das_a, target_das_b)),1604 [bpph.distroarchseries for bpph in bpphs])
1614 set(bpph.distroarchseries for bpph in bpphs))
16151605
1616 def test_architecture_disabled(self):1606 def test_architecture_disabled(self):
1617 # An empty list is return if the DistroArchSeries was disabled.1607 # An empty list is return if the DistroArchSeries was disabled.
@@ -1664,13 +1654,12 @@
1664 build=build, binpackageformat=BinaryPackageFormat.DDEB)1654 build=build, binpackageformat=BinaryPackageFormat.DDEB)
1665 args = self.makeArgs([normal, debug], das.distroseries)1655 args = self.makeArgs([normal, debug], das.distroseries)
1666 bpphs = getUtility(IPublishingSet).publishBinaries(**args)1656 bpphs = getUtility(IPublishingSet).publishBinaries(**args)
1667 self.assertEquals(2, len(bpphs))1657 self.assertEqual(2, len(bpphs))
1668 self.assertEquals(1658 self.assertContentEqual(
1669 set((normal, debug)),1659 (normal, debug), [bpph.binarypackagerelease for bpph in bpphs])
1670 set(bpph.binarypackagerelease for bpph in bpphs))1660 self.assertContentEqual(
1671 self.assertEquals(1661 (das.main_archive, das.main_archive.debug_archive),
1672 set((das.main_archive, das.main_archive.debug_archive)),1662 [bpph.archive for bpph in bpphs])
1673 set(bpph.archive for bpph in bpphs))
16741663
1675 # A second copy does nothing, because it checks in the debug1664 # A second copy does nothing, because it checks in the debug
1676 # archive too.1665 # archive too.
16771666
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2013-01-23 06:54:49 +0000
+++ lib/lp/testing/factory.py 2013-03-08 22:44:24 +0000
@@ -2,7 +2,7 @@
2# NOTE: The first line above must stay first; do not move the copyright2# NOTE: The first line above must stay first; do not move the copyright
3# notice to the top. See http://www.python.org/dev/peps/pep-0263/.3# notice to the top. See http://www.python.org/dev/peps/pep-0263/.
4#4#
5# Copyright 2009-2012 Canonical Ltd. This software is licensed under the5# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
6# GNU Affero General Public License version 3 (see the file LICENSE).6# GNU Affero General Public License version 3 (see the file LICENSE).
77
8"""Testing infrastructure for the Launchpad application.8"""Testing infrastructure for the Launchpad application.
@@ -3678,7 +3678,7 @@
3678 initial source package release upload archive, or to the3678 initial source package release upload archive, or to the
3679 distro series main archive.3679 distro series main archive.
3680 :param pocket: The pocket to publish into. Can be specified as a3680 :param pocket: The pocket to publish into. Can be specified as a
3681 string. Defaults to the RELEASE pocket.3681 string. Defaults to the BACKPORTS pocket.
3682 :param status: The publication status. Defaults to PENDING. If3682 :param status: The publication status. Defaults to PENDING. If
3683 set to PUBLISHED, the publisheddate will be set to now.3683 set to PUBLISHED, the publisheddate will be set to now.
3684 :param dateremoved: The removal date.3684 :param dateremoved: The removal date.