Merge lp:~jtv/launchpad/bug-832661 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Superseded
Proposed branch: lp:~jtv/launchpad/bug-832661
Merge into: lp:launchpad
Diff against target: 654 lines (+323/-98)
9 files modified
lib/lp/soyuz/doc/publishing.txt (+9/-15)
lib/lp/soyuz/interfaces/distributionjob.py (+5/-2)
lib/lp/soyuz/model/archive.py (+3/-6)
lib/lp/soyuz/model/distroseriesdifferencejob.py (+17/-2)
lib/lp/soyuz/model/publishing.py (+68/-72)
lib/lp/soyuz/tests/test_distroseriesdifferencejob.py (+119/-1)
lib/lp/soyuz/tests/test_publishing.py (+70/-0)
lib/lp/testing/factory.py (+18/-0)
lib/lp/testing/tests/test_factory.py (+14/-0)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-832661
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+73009@code.launchpad.net

This proposal has been superseded by a proposal from 2011-08-26.

Commit message

Create DSDJs from requestDeletion.

Description of the change

= Summary =

When we mark a SourcePackagePublishingHistory as deleted in the primary archive of a distroseries that is involved in derivation, we also need to generate a DistroSeriesDifferenceJob for it. SPPH.requestDeletion was already doing this but PublishingSet.requestDeletion was not.

== Proposed fix ==

Move some code around and then leave the real problem — making it scale — for someone else.

== Pre-implementation notes ==

Julian pointed out Archive.is_main; it's likely to be cheaper than checking DistroSeries.main_archive, which issues an extra query at least once.

== Implementation details ==

The core of it is IDistroSeriesDifferenceJobSource.createForSPPHs. You'll find a naïve implementation there that was enough to get me through the tests; since we'll be executing this from Gina (which has a huge backlog of "active" publications that need deleting) and when deleting archives, it's probably important to make it scale better. But given time constraints, I made that a separate work item.

== Tests ==

{{{
./bin/test -vvc lp.soyuz.tests.test_publishing -t requestDeletion
./bin/test -vvc lp.soyuz.tests.test_distroseriesdifferencejob -t createForSPPHs
}}}

== Demo and Q/A ==

Deleting an archive should still mark all its source publication records as deleted.

= Launchpad lint =

The lint is actually remaining pre-existing lint from files I only touched in a previous branch (and fixed in yet another branch), both of which I've been unable to land because of bug 833743 and various other buildbot failures. It's been a wild few days. I'll have to re-submit this branch to register the dependencies on those other branches, so the lint reported here will no longer apply.

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/soyuz/model/distroseriesdifferencejob.py
  lib/lp/soyuz/tests/test_publishing.py
  lib/lp/soyuz/model/publishing.py
  lib/lp/soyuz/doc/publishing.txt
  lib/lp/testing/factory.py
  lib/lp/soyuz/interfaces/distributionjob.py
  lib/lp/soyuz/tests/test_distroseriesdifferencejob.py
  lib/lp/soyuz/model/archive.py
  lib/lp/testing/tests/test_factory.py

./lib/lp/soyuz/doc/publishing.txt
     119: want exceeds 78 characters.
     367: want exceeds 78 characters.
     925: want exceeds 78 characters.
     927: want exceeds 78 characters.
     929: want exceeds 78 characters.
     952: want exceeds 78 characters.
./lib/lp/soyuz/model/archive.py
     350: redefinition of function 'private' from line 346

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/soyuz/doc/publishing.txt'
2--- lib/lp/soyuz/doc/publishing.txt 2011-07-27 23:06:20 +0000
3+++ lib/lp/soyuz/doc/publishing.txt 2011-08-26 09:41:33 +0000
4@@ -30,12 +30,14 @@
5 >>> from canonical.launchpad.webapp.testing import verifyObject
6 >>> from lp.registry.interfaces.distroseries import IDistroSeries
7 >>> from lp.registry.interfaces.sourcepackage import ISourcePackage
8- >>> from lp.soyuz.interfaces.distributionsourcepackagerelease import IDistributionSourcePackageRelease
9+ >>> from lp.soyuz.interfaces.distributionsourcepackagerelease import (
10+ ... IDistributionSourcePackageRelease)
11 >>> from lp.soyuz.interfaces.publishing import (
12 ... IBinaryPackagePublishingHistory,
13 ... ISourcePackagePublishingHistory,
14 ... )
15- >>> from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
16+ >>> from lp.soyuz.interfaces.sourcepackagerelease import (
17+ ... ISourcePackageRelease)
18
19 >>> verifyObject(ISourcePackagePublishingHistory, spph)
20 True
21@@ -769,7 +771,7 @@
22 ... ppa_copied_source.id)
23
24 createMissingBuilds will not create any builds because this is an
25-intra-archive copy:
26+intra-archive copy:
27
28 >>> ppa_source.createMissingBuilds()
29 []
30@@ -901,7 +903,8 @@
31 Symmetric behaviour is offered for BinaryPackagePublishing,
32 BinaryPackageFile and IBinaryPackagePublishingHistory
33
34- >>> from lp.soyuz.interfaces.publishing import IBinaryPackageFilePublishing
35+ >>> from lp.soyuz.interfaces.publishing import (
36+ ... IBinaryPackageFilePublishing)
37
38 >>> bpph = BinaryPackagePublishingHistory.get(15)
39 >>> print bpph.displayname
40@@ -1470,15 +1473,6 @@
41 >>> cprov_binaries.count()
42 8
43
44-Please note also that the source publishing records to be deleted must
45-be passed as a list. Otherwise an exception will be raised.
46-
47- >>> deleted = publishing_set.requestDeletion(
48- ... tuple(cprov_sources[:2]), cprov, 'OOPS-934EC47')
49- Traceback (most recent call last):
50- ...
51- AssertionError: The 'sources' parameter must be a list.
52-
53
54 ArchiveSourcePublications
55 =========================
56@@ -1491,8 +1485,8 @@
57 fetch in a fixed number of queries (3) instead of varying according
58 the size of the set (3 * N).
59
60- >>> from lp.soyuz.adapters.archivesourcepublication import (
61- ... ArchiveSourcePublications)
62+ >>> from lp.soyuz.adapters.archivesourcepublication import (
63+ ... ArchiveSourcePublications)
64
65 We will use all published sources in Celso's PPA as our initial set.
66
67
68=== modified file 'lib/lp/soyuz/interfaces/distributionjob.py'
69--- lib/lp/soyuz/interfaces/distributionjob.py 2011-07-27 10:34:53 +0000
70+++ lib/lp/soyuz/interfaces/distributionjob.py 2011-08-26 09:41:33 +0000
71@@ -1,4 +1,4 @@
72-# Copyright 2010 Canonical Ltd. This software is licensed under the
73+# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
74 # GNU Affero General Public License version 3 (see the file LICENSE).
75
76 __metaclass__ = type
77@@ -122,7 +122,7 @@
78 """An `IJob` for creating `DistroSeriesDifference`s."""
79
80 def createForPackagePublication(derivedseries, sourcepackagename, pocket):
81- """Create jobs as appropriate for a given status publication.
82+ """Create jobs as appropriate for a given package publication.
83
84 :param derived_series: A `DistroSeries` that is assumed to be
85 derived from `parent_series`.
86@@ -132,6 +132,9 @@
87 :return: An iterable of `DistroSeriesDifferenceJob`.
88 """
89
90+ def createForSPPHs(spphs):
91+ """Create jobs for given `SourcePackagePublishingHistory`s."""
92+
93 def massCreateForSeries(derived_series):
94 """Create jobs for all the publications inside the given distroseries
95 with reference to the given parent series.
96
97=== modified file 'lib/lp/soyuz/model/archive.py'
98--- lib/lp/soyuz/model/archive.py 2011-08-24 06:09:05 +0000
99+++ lib/lp/soyuz/model/archive.py 2011-08-26 09:41:33 +0000
100@@ -58,8 +58,8 @@
101 DecoratedResultSet,
102 )
103 from canonical.launchpad.components.tokens import (
104+ create_token,
105 create_unique_token_for_table,
106- create_token,
107 )
108 from canonical.launchpad.database.librarian import (
109 LibraryFileAlias,
110@@ -150,8 +150,8 @@
111 NoSuchPPA,
112 NoTokensForTeams,
113 PocketNotFound,
114+ validate_external_dependencies,
115 VersionRequiresName,
116- validate_external_dependencies,
117 )
118 from lp.soyuz.interfaces.archivearch import IArchiveArchSet
119 from lp.soyuz.interfaces.archiveauthtoken import IArchiveAuthTokenSet
120@@ -1881,10 +1881,7 @@
121 "This archive is already deleted.")
122
123 # Set all the publications to DELETED.
124- statuses = (
125- PackagePublishingStatus.PENDING,
126- PackagePublishingStatus.PUBLISHED)
127- sources = list(self.getPublishedSources(status=statuses))
128+ sources = self.getPublishedSources(status=active_publishing_status)
129 getUtility(IPublishingSet).requestDeletion(
130 sources, removed_by=deleted_by,
131 removal_comment="Removed when deleting archive")
132
133=== modified file 'lib/lp/soyuz/model/distroseriesdifferencejob.py'
134--- lib/lp/soyuz/model/distroseriesdifferencejob.py 2011-08-09 10:01:02 +0000
135+++ lib/lp/soyuz/model/distroseriesdifferencejob.py 2011-08-26 09:41:33 +0000
136@@ -195,13 +195,16 @@
137 """See `IDistroSeriesDifferenceJobSource`."""
138 if not getFeatureFlag(FEATURE_FLAG_ENABLE_MODULE):
139 return
140+
141 # -backports and -proposed are not really part of a standard
142 # distribution's packages so we're ignoring them here. They can
143 # always be manually synced by the users if necessary, in the
144 # rare occasions that they require them.
145- if pocket in (
146+ ignored_pockets = [
147 PackagePublishingPocket.BACKPORTS,
148- PackagePublishingPocket.PROPOSED):
149+ PackagePublishingPocket.PROPOSED,
150+ ]
151+ if pocket in ignored_pockets:
152 return
153
154 # Create jobs for DSDs between the derived_series' parents and
155@@ -221,6 +224,18 @@
156 return parent_series_jobs + derived_series_jobs
157
158 @classmethod
159+ def createForSPPHs(cls, spphs):
160+ """See `IDistroSeriesDifferenceJobSource`."""
161+ # XXX JeroenVermeulen 2011-08-25, bug=834499: This won't do for
162+ # some of the mass deletions we're planning to support.
163+ # Optimize.
164+ for spph in spphs:
165+ if spph.archive.is_main:
166+ cls.createForPackagePublication(
167+ spph.distroseries,
168+ spph.sourcepackagerelease.sourcepackagename, spph.pocket)
169+
170+ @classmethod
171 def massCreateForSeries(cls, derived_series):
172 """See `IDistroSeriesDifferenceJobSource`."""
173 if not getFeatureFlag(FEATURE_FLAG_ENABLE_MODULE):
174
175=== modified file 'lib/lp/soyuz/model/publishing.py'
176--- lib/lp/soyuz/model/publishing.py 2011-07-28 18:37:47 +0000
177+++ lib/lp/soyuz/model/publishing.py 2011-08-26 09:41:33 +0000
178@@ -332,18 +332,12 @@
179 self.status = PackagePublishingStatus.SUPERSEDED
180 self.datesuperseded = UTC_NOW
181
182- def requestDeletion(self, removed_by, removal_comment=None):
183- """See `IPublishing`."""
184+ def setDeleted(self, removed_by, removal_comment=None):
185+ """Set to DELETED status."""
186 self.status = PackagePublishingStatus.DELETED
187 self.datesuperseded = UTC_NOW
188 self.removed_by = removed_by
189 self.removal_comment = removal_comment
190- if ISourcePackagePublishingHistory.providedBy(self):
191- if self.archive == self.distroseries.main_archive:
192- dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)
193- dsd_job_source.createForPackagePublication(
194- self.distroseries,
195- self.sourcepackagerelease.sourcepackagename, self.pocket)
196
197 def requestObsolescence(self):
198 """See `IArchivePublisher`."""
199@@ -894,6 +888,15 @@
200 diff.diff_content, self.archive).http_url
201 return None
202
203+ def requestDeletion(self, removed_by, removal_comment=None):
204+ """See `IPublishing`."""
205+ self.setDeleted(removed_by, removal_comment)
206+ if self.archive.is_main:
207+ dsd_job_source = getUtility(IDistroSeriesDifferenceJobSource)
208+ dsd_job_source.createForPackagePublication(
209+ self.distroseries,
210+ self.sourcepackagerelease.sourcepackagename, self.pocket)
211+
212 def api_requestDeletion(self, removed_by, removal_comment=None):
213 """See `IPublishingEdit`."""
214 # Special deletion method for the api that makes sure binaries
215@@ -1296,6 +1299,10 @@
216 # different here (yet).
217 self.requestDeletion(removed_by, removal_comment)
218
219+ def requestDeletion(self, removed_by, removal_comment=None):
220+ """See `IPublishing`."""
221+ self.setDeleted(removed_by, removal_comment)
222+
223
224 def expand_binary_requests(distroseries, binaries):
225 """Architecture-expand a dict of binary publication requests.
226@@ -1761,14 +1768,11 @@
227
228 return result_set
229
230- def getBinaryPublicationsForSources(
231- self, one_or_more_source_publications):
232+ def getBinaryPublicationsForSources(self,
233+ one_or_more_source_publications):
234 """See `IPublishingSet`."""
235- # Import Buildand DistroArchSeries locally to avoid circular imports,
236- # since Build uses SourcePackagePublishingHistory and DistroArchSeries
237- # uses BinaryPackagePublishingHistory.
238- from lp.soyuz.model.distroarchseries import (
239- DistroArchSeries)
240+ # Avoid circular imports.
241+ from lp.soyuz.model.distroarchseries import DistroArchSeries
242
243 source_publication_ids = self._extractIDs(
244 one_or_more_source_publications)
245@@ -1955,66 +1959,58 @@
246 return self.getBuildStatusSummariesForSourceIdsAndArchive([source_id],
247 source_publication.archive)[source_id]
248
249+ def setMultipleDeleted(self, publication_class, ids, removed_by,
250+ removal_comment=None):
251+ """Mark multiple publication records as deleted."""
252+ ids = list(ids)
253+ if len(ids) == 0:
254+ return
255+
256+ table = publication_class.__name__
257+ permitted_tables = [
258+ 'BinaryPackagePublishingHistory',
259+ 'SourcePackagePublishingHistory',
260+ ]
261+ assert table in permitted_tables, "Deleting wrong type."
262+
263+ params = sqlvalues(
264+ deleted=PackagePublishingStatus.DELETED, now=UTC_NOW,
265+ removal_comment=removal_comment, removed_by=removed_by)
266+
267+ IMasterStore(publication_class).execute("\n".join([
268+ "UPDATE %s" % table,
269+ """
270+ SET
271+ status = %(deleted)s,
272+ datesuperseded = %(now)s,
273+ removed_by = %(removed_by)s,
274+ removal_comment = %(removal_comment)s
275+ """ % params,
276+ "WHERE id IN %s" % sqlvalues(ids),
277+ ]))
278+
279 def requestDeletion(self, sources, removed_by, removal_comment=None):
280 """See `IPublishingSet`."""
281-
282- # The 'sources' parameter could actually be any kind of sequence
283- # (e.g. even a ResultSet) and the method would still work correctly.
284- # This is problematic when it comes to the type of the return value
285- # however.
286- # Apparently the caller anticipates that we return the sequence of
287- # instances "deleted" adhering to the original type of the 'sources'
288- # parameter.
289- # Since this is too messy we prescribe that the type of 'sources'
290- # must be a list and we return the instances manipulated as a list.
291- # This may not be an ideal solution but this way we at least achieve
292- # consistency.
293- assert isinstance(sources, list), (
294- "The 'sources' parameter must be a list.")
295-
296+ sources = list(sources)
297 if len(sources) == 0:
298- return []
299-
300- # The following piece of query "boiler plate" will be used for
301- # both the source and the binary package publishing history table.
302- query_boilerplate = '''
303- SET status = %s,
304- datesuperseded = %s,
305- removed_by = %s,
306- removal_comment = %s
307- WHERE id IN
308- ''' % sqlvalues(PackagePublishingStatus.DELETED, UTC_NOW,
309- removed_by, removal_comment)
310-
311- store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
312-
313- # First update the source package publishing history table.
314- source_ids = [source.id for source in sources]
315- if len(source_ids) > 0:
316- query = 'UPDATE SourcePackagePublishingHistory '
317- query += query_boilerplate
318- query += ' %s' % sqlvalues(source_ids)
319- store.execute(query)
320-
321- # Prepare the list of associated *binary* packages publishing
322- # history records.
323- binary_packages = []
324- for source in sources:
325- binary_packages.extend(source.getPublishedBinaries())
326-
327- if len(binary_packages) == 0:
328- return sources
329-
330- # Now run the query that marks the binary packages as deleted
331- # as well.
332- if len(binary_packages) > 0:
333- query = 'UPDATE BinaryPackagePublishingHistory '
334- query += query_boilerplate
335- query += ' %s' % sqlvalues(
336- [binary.id for binary in binary_packages])
337- store.execute(query)
338-
339- return sources + binary_packages
340+ return
341+
342+ spph_ids = [spph.id for spph in sources]
343+ self.setMultipleDeleted(
344+ SourcePackagePublishingHistory, spph_ids, removed_by,
345+ removal_comment=removal_comment)
346+
347+ getUtility(IDistroSeriesDifferenceJobSource).createForSPPHs(sources)
348+
349+ # Mark binary publications deleted.
350+ bpph_ids = [
351+ bpph.id
352+ for source, bpph, bin, bin_name, arch
353+ in self.getBinaryPublicationsForSources(sources)]
354+ if len(bpph_ids) > 0:
355+ self.setMultipleDeleted(
356+ BinaryPackagePublishingHistory, bpph_ids, removed_by,
357+ removal_comment=removal_comment)
358
359 def getNearestAncestor(
360 self, package_name, archive, distroseries, pocket=None,
361
362=== modified file 'lib/lp/soyuz/tests/test_distroseriesdifferencejob.py'
363--- lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-08-16 10:11:32 +0000
364+++ lib/lp/soyuz/tests/test_distroseriesdifferencejob.py 2011-08-26 09:41:33 +0000
365@@ -27,7 +27,10 @@
366 from lp.services.database import bulk
367 from lp.services.features.testing import FeatureFixture
368 from lp.services.job.interfaces.job import JobStatus
369-from lp.soyuz.enums import PackagePublishingStatus
370+from lp.soyuz.enums import (
371+ ArchivePurpose,
372+ PackagePublishingStatus,
373+ )
374 from lp.soyuz.interfaces.distributionjob import (
375 DistributionJobType,
376 IDistroSeriesDifferenceJobSource,
377@@ -322,6 +325,121 @@
378 [],
379 find_waiting_jobs(dsp.derived_series, package, dsp.parent_series))
380
381+ def test_createForSPPHs_creates_job_for_derived_series(self):
382+ dsp = self.factory.makeDistroSeriesParent()
383+ spph = self.factory.makeSourcePackagePublishingHistory(
384+ dsp.parent_series, pocket=PackagePublishingPocket.RELEASE)
385+ spn = spph.sourcepackagerelease.sourcepackagename
386+
387+ self.getJobSource().createForSPPHs([spph])
388+
389+ self.assertEqual(
390+ 1, len(find_waiting_jobs(
391+ dsp.derived_series, spn, dsp.parent_series)))
392+
393+ def test_createForSPPHs_creates_job_for_parent_series(self):
394+ dsp = self.factory.makeDistroSeriesParent()
395+ spph = self.factory.makeSourcePackagePublishingHistory(
396+ dsp.derived_series, pocket=PackagePublishingPocket.RELEASE)
397+ spn = spph.sourcepackagerelease.sourcepackagename
398+
399+ self.getJobSource().createForSPPHs([spph])
400+
401+ self.assertEqual(
402+ 1, len(find_waiting_jobs(
403+ dsp.derived_series, spn, dsp.parent_series)))
404+
405+ def test_createForSPPHs_obeys_feature_flag(self):
406+ self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: ''}))
407+ dsp = self.factory.makeDistroSeriesParent()
408+ spph = self.factory.makeSourcePackagePublishingHistory(
409+ dsp.parent_series, pocket=PackagePublishingPocket.RELEASE)
410+ spn = spph.sourcepackagerelease.sourcepackagename
411+ self.getJobSource().createForSPPHs([spph])
412+ self.assertContentEqual(
413+ [], find_waiting_jobs(dsp.derived_series, spn, dsp.parent_series))
414+
415+ def test_createForSPPHs_ignores_backports_and_proposed(self):
416+ dsp = self.factory.makeDistroSeriesParent()
417+ spr = self.factory.makeSourcePackageRelease()
418+ spn = spr.sourcepackagename
419+ ignored_pockets = [
420+ PackagePublishingPocket.BACKPORTS,
421+ PackagePublishingPocket.PROPOSED,
422+ ]
423+ spphs = [
424+ self.factory.makeSourcePackagePublishingHistory(
425+ distroseries=dsp.parent_series, sourcepackagerelease=spr,
426+ pocket=pocket)
427+ for pocket in ignored_pockets]
428+ self.getJobSource().createForSPPHs(spphs)
429+ self.assertContentEqual(
430+ [], find_waiting_jobs(dsp.derived_series, spn, dsp.parent_series))
431+
432+ def test_createForSPPHs_creates_no_jobs_for_unrelated_series(self):
433+ dsp = self.factory.makeDistroSeriesParent()
434+ other_series = self.factory.makeDistroSeries(
435+ distribution=dsp.derived_series.distribution)
436+ spph = self.factory.makeSourcePackagePublishingHistory(
437+ dsp.parent_series, pocket=PackagePublishingPocket.RELEASE)
438+ spn = spph.sourcepackagerelease.sourcepackagename
439+ self.getJobSource().createForSPPHs([spph])
440+ self.assertContentEqual(
441+ [], find_waiting_jobs(dsp.derived_series, spn, other_series))
442+
443+ def test_createForSPPHs_accepts_SPPHs_for_multiple_distroseries(self):
444+ derived_distro = self.factory.makeDistribution()
445+ spn = self.factory.makeSourcePackageName()
446+ series = [
447+ self.factory.makeDistroSeries(derived_distro)
448+ for counter in xrange(2)]
449+ dsps = [
450+ self.factory.makeDistroSeriesParent(derived_series=distroseries)
451+ for distroseries in series]
452+
453+ for distroseries in series:
454+ self.factory.makeSourcePackagePublishingHistory(
455+ distroseries, pocket=PackagePublishingPocket.RELEASE,
456+ sourcepackagerelease=self.factory.makeSourcePackageRelease(
457+ sourcepackagename=spn))
458+
459+ job_counts = dict(
460+ (dsp.derived_series, len(find_waiting_jobs(
461+ dsp.derived_series, spn, dsp.parent_series)))
462+ for dsp in dsps)
463+ self.assertEqual(
464+ dict((distroseries, 1) for distroseries in series),
465+ job_counts)
466+
467+ def test_createForSPPHs_behaves_sensibly_if_job_already_exists(self):
468+ # If a job already existed, createForSPPHs may create a
469+ # redundant one but it certainly won't do anything weird like
470+ # delete what was there or create too many.
471+ dsp = self.factory.makeDistroSeriesParent()
472+ spph = self.factory.makeSourcePackagePublishingHistory(
473+ dsp.parent_series, pocket=PackagePublishingPocket.RELEASE)
474+ spn = spph.sourcepackagerelease.sourcepackagename
475+
476+ create_jobs = range(1, 3)
477+ for counter in create_jobs:
478+ self.getJobSource().createForSPPHs([spph])
479+
480+ job_count = len(find_waiting_jobs(
481+ dsp.derived_series, spn, dsp.parent_series))
482+ self.assertIn(job_count, create_jobs)
483+
484+ def test_createForSPPHs_creates_no_jobs_for_ppas(self):
485+ dsp = self.factory.makeDistroSeriesParent()
486+ series = dsp.parent_series
487+ spph = self.factory.makeSourcePackagePublishingHistory(
488+ series, pocket=PackagePublishingPocket.RELEASE,
489+ archive=self.factory.makeArchive(
490+ distribution=series.distribution, purpose=ArchivePurpose.PPA))
491+ spn = spph.sourcepackagerelease.sourcepackagename
492+ self.getJobSource().createForSPPHs([spph])
493+ self.assertContentEqual(
494+ [], find_waiting_jobs(dsp.derived_series, spn, dsp.parent_series))
495+
496 def test_massCreateForSeries_obeys_feature_flag(self):
497 self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: ''}))
498 dsp = self.factory.makeDistroSeriesParent()
499
500=== modified file 'lib/lp/soyuz/tests/test_publishing.py'
501--- lib/lp/soyuz/tests/test_publishing.py 2011-08-25 06:18:33 +0000
502+++ lib/lp/soyuz/tests/test_publishing.py 2011-08-26 09:41:33 +0000
503@@ -25,6 +25,7 @@
504 DatabaseFunctionalLayer,
505 LaunchpadZopelessLayer,
506 reconnect_stores,
507+ ZopelessDatabaseLayer,
508 )
509 from lp.app.errors import NotFoundError
510 from lp.archivepublisher.config import getPubConfig
511@@ -36,6 +37,7 @@
512 from lp.registry.interfaces.pocket import PackagePublishingPocket
513 from lp.registry.interfaces.sourcepackage import SourcePackageUrgency
514 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
515+from lp.services.features.testing import FeatureFixture
516 from lp.services.log.logger import DevNullLogger
517 from lp.soyuz.adapters.overrides import UnknownOverridePolicy
518 from lp.soyuz.enums import (
519@@ -54,6 +56,10 @@
520 )
521 from lp.soyuz.interfaces.queue import QueueInconsistentStateError
522 from lp.soyuz.interfaces.section import ISectionSet
523+from lp.soyuz.model.distroseriesdifferencejob import (
524+ FEATURE_FLAG_ENABLE_MODULE,
525+ find_waiting_jobs,
526+ )
527 from lp.soyuz.model.distroseriespackagecache import DistroSeriesPackageCache
528 from lp.soyuz.model.processor import ProcessorFamily
529 from lp.soyuz.model.publishing import (
530@@ -1162,6 +1168,70 @@
531 self.assertEqual(None, record)
532
533
534+class TestPublishingSetLite(TestCaseWithFactory):
535+
536+ layer = ZopelessDatabaseLayer
537+
538+ def enableDistroDerivation(self):
539+ self.useFixture(FeatureFixture({FEATURE_FLAG_ENABLE_MODULE: u'on'}))
540+
541+ def test_requestDeletion_marks_SPPHs_deleted(self):
542+ spph = self.factory.makeSourcePackagePublishingHistory()
543+ getUtility(IPublishingSet).requestDeletion(
544+ [spph], self.factory.makePerson())
545+ # XXX JeroenVermeulen 2011-08-25, bug=834388: obviate commit.
546+ transaction.commit()
547+ self.assertEqual(PackagePublishingStatus.DELETED, spph.status)
548+
549+ def test_requestDeletion_leaves_other_SPPHs_alone(self):
550+ spph = self.factory.makeSourcePackagePublishingHistory()
551+ other_spph = self.factory.makeSourcePackagePublishingHistory()
552+ getUtility(IPublishingSet).requestDeletion(
553+ [other_spph], self.factory.makePerson())
554+ # XXX JeroenVermeulen 2011-08-25, bug=834388: obviate commit.
555+ transaction.commit()
556+ self.assertEqual(PackagePublishingStatus.PENDING, spph.status)
557+
558+ def test_requestDeletion_marks_attached_BPPHs_deleted(self):
559+ bpph = self.factory.makeBinaryPackagePublishingHistory()
560+ spph = self.factory.makeSPPHForBPPH(bpph)
561+ getUtility(IPublishingSet).requestDeletion(
562+ [spph], self.factory.makePerson())
563+ # XXX JeroenVermeulen 2011-08-25, bug=834388: obviate commit.
564+ transaction.commit()
565+ self.assertEqual(PackagePublishingStatus.DELETED, spph.status)
566+
567+ def test_requestDeletion_leaves_other_BPPHs_alone(self):
568+ bpph = self.factory.makeBinaryPackagePublishingHistory()
569+ unrelated_spph = self.factory.makeSourcePackagePublishingHistory()
570+ getUtility(IPublishingSet).requestDeletion(
571+ [unrelated_spph], self.factory.makePerson())
572+ # XXX JeroenVermeulen 2011-08-25, bug=834388: obviate commit.
573+ transaction.commit()
574+ self.assertEqual(PackagePublishingStatus.PENDING, bpph.status)
575+
576+ def test_requestDeletion_accepts_empty_sources_list(self):
577+ person = self.factory.makePerson()
578+ getUtility(IPublishingSet).requestDeletion([], person)
579+ # The test is that this does not fail.
580+ Store.of(person).flush()
581+
582+ def test_requestDeletion_creates_DistroSeriesDifferenceJobs(self):
583+ dsp = self.factory.makeDistroSeriesParent()
584+ series = dsp.derived_series
585+ spph = self.factory.makeSourcePackagePublishingHistory(
586+ series, pocket=PackagePublishingPocket.RELEASE)
587+ spn = spph.sourcepackagerelease.sourcepackagename
588+
589+ self.enableDistroDerivation()
590+ getUtility(IPublishingSet).requestDeletion(
591+ [spph], self.factory.makePerson())
592+
593+ self.assertEqual(
594+ 1, len(find_waiting_jobs(
595+ dsp.derived_series, spn, dsp.parent_series)))
596+
597+
598 class TestSourceDomination(TestNativePublishingBase):
599 """Test SourcePackagePublishingHistory.supersede() operates correctly."""
600
601
602=== modified file 'lib/lp/testing/factory.py'
603--- lib/lp/testing/factory.py 2011-08-16 09:14:07 +0000
604+++ lib/lp/testing/factory.py 2011-08-26 09:41:33 +0000
605@@ -3799,6 +3799,24 @@
606 naked_bpph.datepublished = UTC_NOW
607 return bpph
608
609+ def makeSPPHForBPPH(self, bpph):
610+ """Produce a `SourcePackagePublishingHistory` to match `bpph`.
611+
612+ :param bpph: A `BinaryPackagePublishingHistory`.
613+ :return: A `SourcePackagePublishingHistory` stemming from the same
614+ source package as `bpph`, published into the same distroseries,
615+ pocket, and archive.
616+ """
617+ # JeroenVermeulen 2011-08-25, bug=834370: Julian says this isn't
618+ # very complete, and ignores architectures. Improve so we can
619+ # remove more of our reliance on the SoyuzTestPublisher.
620+ bpr = bpph.binarypackagerelease
621+ spph = self.makeSourcePackagePublishingHistory(
622+ distroseries=bpph.distroarchseries.distroseries,
623+ sourcepackagerelease=bpr.build.source_package_release,
624+ pocket=bpph.pocket, archive=bpph.archive)
625+ return spph
626+
627 def makeBinaryPackageName(self, name=None):
628 """Make an `IBinaryPackageName`."""
629 if name is None:
630
631=== modified file 'lib/lp/testing/tests/test_factory.py'
632--- lib/lp/testing/tests/test_factory.py 2011-08-03 11:00:11 +0000
633+++ lib/lp/testing/tests/test_factory.py 2011-08-26 09:41:33 +0000
634@@ -519,6 +519,20 @@
635 dsc_maintainer_rfc822=maintainer)
636 self.assertEqual(maintainer, spr.dsc_maintainer_rfc822)
637
638+ # makeSPPHForBPPH
639+ def test_makeSPPHForBPPH_returns_ISPPH(self):
640+ bpph = self.factory.makeBinaryPackagePublishingHistory()
641+ spph = self.factory.makeSPPHForBPPH(bpph)
642+ self.assertThat(spph, IsProxied())
643+ self.assertThat(
644+ removeSecurityProxy(spph),
645+ Provides(ISourcePackagePublishingHistory))
646+
647+ def test_makeSPPHForBPPH_returns_SPPH_for_BPPH(self):
648+ bpph = self.factory.makeBinaryPackagePublishingHistory()
649+ spph = self.factory.makeSPPHForBPPH(bpph)
650+ self.assertContentEqual([bpph], spph.getPublishedBinaries())
651+
652 # makeSuiteSourcePackage
653 def test_makeSuiteSourcePackage_returns_ISuiteSourcePackage(self):
654 ssp = self.factory.makeSuiteSourcePackage()