Merge lp:~cjwatson/launchpad/copy-custom-uploads into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Benji York
Approved revision: no longer in the source branch.
Merged at revision: 15491
Proposed branch: lp:~cjwatson/launchpad/copy-custom-uploads
Merge into: lp:launchpad
Diff against target: 959 lines (+226/-157)
8 files modified
database/schema/security.cfg (+1/-0)
lib/lp/archivepublisher/scripts/publish_ftpmaster.py (+9/-2)
lib/lp/soyuz/scripts/custom_uploads_copier.py (+21/-10)
lib/lp/soyuz/scripts/packagecopier.py (+17/-1)
lib/lp/soyuz/scripts/tests/test_copypackage.py (+126/-128)
lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py (+34/-5)
lib/lp/testing/factory.py (+11/-7)
lib/lp/testing/tests/test_factory.py (+7/-4)
To merge this branch: bzr merge lp:~cjwatson/launchpad/copy-custom-uploads
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Brad Crittenden (community) code Abstain
Review via email: mp+111653@code.launchpad.net

Commit message

Handle custom files in direct copies (and hence PCJs) by synthesising an upload.

Description of the change

== Summary ==

One of the last bugs that cause Ubuntu archive administrators to still require access to lp_publish@cocoplum (a horrendously privileged account) is bug 231371: if we upload installer or upgrader images to -proposed, get them past verification, and copy them to -updates, we have to copy the custom files around by hand. At the moment we have a pile of manual instructions in https://wiki.ubuntu.com/ArchiveAdministration#Moving_Packages_to_Updates for this, but obviously this should be automated.

== Proposed fix ==

A while back, Jeroen wrote CustomUploadsCopier, which deals with copying custom files across when we open a new distroseries. I always rather suspected that it wouldn't be too difficult to hook it up to solve this problem too, and it looks as though I was right. :-)

This does make one aspect of direct copies behave a little bit like delayed copies in that they synthesise an upload, and I considered making this case trigger a delayed copy, but consensus seems to be that delayed copies should be removed at the earliest opportunity, and I can't say I disagree. Besides, CustomUploadsCopier.copyUpload does enough of the work for us that I think it would actually be more code to reuse delayed copies for this.

== LOC Rationale ==

I got it down to +68 with a bit of judicious tidying up of unit tests. Aside from that, this is part of the general arc of tidying up package copying described in https://code.launchpad.net/~cjwatson/launchpad/pcj-reupload/+merge/111124; as indicated there, even with the addition of this branch that's going to come out substantially LoC-negative.

== Tests ==

bin/test -vvct test_copypackage -t test_custom_uploads_copier -t test_factory -t test_publish_ftpmaster

== Demo and Q/A ==

This will involve two publisher runs on dogfood, so it won't be exactly fast, but it shouldn't be difficult. Upload debian-installer to precise-proposed on dogfood, let it build, publish it, then use Archive.copyPackage (or 'sru-release -l dogfood' from lp:ubuntu-archive-tools) to copy it to precise-updates. This should create the appropriate versioned subdirectories and "current" symlinks in dists/precise-updates/main/installer-*/.

== Lint ==

Just one pre-existing and not very interesting entry:

./lib/lp/soyuz/scripts/tests/test_copypackage.py
    1450: E501 line too long (82 characters)

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) :
review: Abstain (code)
Revision history for this message
Benji York (benji) wrote :

This branch looks good. The replacing of assertEquals with assertEqual is appreciated.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2012-06-22 05:36:22 +0000
3+++ database/schema/security.cfg 2012-06-25 16:57:21 +0000
4@@ -1034,6 +1034,7 @@
5 public.packageupload = SELECT
6 public.packageuploadsource = SELECT
7 public.packageuploadbuild = SELECT
8+public.packageuploadcustom = SELECT
9 public.packaging = SELECT, INSERT
10 public.person = SELECT
11 public.pocketchroot = SELECT
12
13=== modified file 'lib/lp/archivepublisher/scripts/publish_ftpmaster.py'
14--- lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2012-05-23 00:20:23 +0000
15+++ lib/lp/archivepublisher/scripts/publish_ftpmaster.py 2012-06-25 16:57:21 +0000
16@@ -18,7 +18,10 @@
17 from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
18 from lp.archivepublisher.publishing import GLOBAL_PUBLISHER_LOCK
19 from lp.registry.interfaces.distribution import IDistributionSet
20-from lp.registry.interfaces.pocket import pocketsuffix
21+from lp.registry.interfaces.pocket import (
22+ PackagePublishingPocket,
23+ pocketsuffix,
24+ )
25 from lp.registry.interfaces.series import SeriesStatus
26 from lp.services.config import config
27 from lp.services.database.bulk import load_related
28@@ -588,7 +591,11 @@
29 # This is a fresh series.
30 have_fresh_series = True
31 if series.previous_series is not None:
32- CustomUploadsCopier(series).copy(series.previous_series)
33+ copier = CustomUploadsCopier(
34+ series, PackagePublishingPocket.RELEASE)
35+ copier.copy(
36+ series.previous_series,
37+ PackagePublishingPocket.RELEASE)
38 self.createIndexes(distribution, suites_needing_indexes)
39
40 return have_fresh_series
41
42=== modified file 'lib/lp/soyuz/scripts/custom_uploads_copier.py'
43--- lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-05-30 10:25:43 +0000
44+++ lib/lp/soyuz/scripts/custom_uploads_copier.py 2012-06-25 16:57:21 +0000
45@@ -42,17 +42,20 @@
46 PackageUploadCustomFormat.DIST_UPGRADER: DistUpgraderUpload,
47 }
48
49- def __init__(self, target_series):
50+ def __init__(self, target_series,
51+ target_pocket=PackagePublishingPocket.RELEASE):
52 self.target_series = target_series
53+ self.target_pocket = target_pocket
54
55 def isCopyable(self, upload):
56 """Is `upload` the kind of `PackageUploadCustom` that we can copy?"""
57 return upload.customformat in self.copyable_types
58
59- def getCandidateUploads(self, source_series):
60+ def getCandidateUploads(self, source_series,
61+ source_pocket=PackagePublishingPocket.RELEASE):
62 """Find custom uploads that may need copying."""
63 uploads = source_series.getPackageUploads(
64- custom_type=self.copyable_types.keys())
65+ pocket=source_pocket, custom_type=self.copyable_types.keys())
66 load_referencing(PackageUploadCustom, uploads, ['packageuploadID'])
67 customs = sum([list(upload.customfiles) for upload in uploads], [])
68 customs = filter(self.isCopyable, customs)
69@@ -77,15 +80,19 @@
70 else:
71 return (custom_format, series_key)
72
73- def getLatestUploads(self, source_series):
74+ def getLatestUploads(self, source_series,
75+ source_pocket=PackagePublishingPocket.RELEASE):
76 """Find the latest uploads.
77
78 :param source_series: The `DistroSeries` whose uploads to get.
79+ :param source_pocket: The `PackagePublishingPocket` to inspect.
80 :return: A dict containing the latest uploads, indexed by keys as
81 returned by `getKey`.
82 """
83+ candidate_uploads = self.getCandidateUploads(
84+ source_series, source_pocket=source_pocket)
85 latest_uploads = {}
86- for upload in self.getCandidateUploads(source_series):
87+ for upload in candidate_uploads:
88 key = self.getKey(upload)
89 if key is not None:
90 latest_uploads.setdefault(key, upload)
91@@ -120,16 +127,20 @@
92 if target_archive is None:
93 return None
94 package_upload = self.target_series.createQueueEntry(
95- PackagePublishingPocket.RELEASE, target_archive,
96+ self.target_pocket, target_archive,
97 changes_file_alias=original_upload.packageupload.changesfile)
98 custom = package_upload.addCustom(
99 original_upload.libraryfilealias, original_upload.customformat)
100 package_upload.setAccepted()
101 return custom
102
103- def copy(self, source_series):
104- """Copy uploads from `source_series`."""
105- target_uploads = self.getLatestUploads(self.target_series)
106- for upload in self.getLatestUploads(source_series).itervalues():
107+ def copy(self, source_series,
108+ source_pocket=PackagePublishingPocket.RELEASE):
109+ """Copy uploads from `source_series`-`source_pocket`."""
110+ target_uploads = self.getLatestUploads(
111+ self.target_series, source_pocket=self.target_pocket)
112+ source_uploads = self.getLatestUploads(
113+ source_series, source_pocket=source_pocket)
114+ for upload in source_uploads.itervalues():
115 if not self.isObsolete(upload, target_uploads):
116 self.copyUpload(upload)
117
118=== modified file 'lib/lp/soyuz/scripts/packagecopier.py'
119--- lib/lp/soyuz/scripts/packagecopier.py 2012-06-22 23:55:02 +0000
120+++ lib/lp/soyuz/scripts/packagecopier.py 2012-06-25 16:57:21 +0000
121@@ -49,6 +49,7 @@
122 IPackageUploadCustom,
123 IPackageUploadSet,
124 )
125+from lp.soyuz.scripts.custom_uploads_copier import CustomUploadsCopier
126 from lp.soyuz.scripts.ftpmasterbase import (
127 SoyuzScript,
128 SoyuzScriptError,
129@@ -731,6 +732,7 @@
130 publications.
131 """
132 copies = []
133+ custom_files = []
134
135 # Copy source if it's not yet copied.
136 source_in_destination = archive.getPublishedSources(
137@@ -762,6 +764,8 @@
138 copies.append(source_copy)
139 else:
140 source_copy = source_in_destination.first()
141+ if source_copy.packageupload is not None:
142+ custom_files.extend(source_copy.packageupload.customfiles)
143
144 if include_binaries:
145 # Copy missing binaries for the matching architectures in the
146@@ -771,10 +775,22 @@
147 # arch-indep publications.
148 binary_copies = getUtility(IPublishingSet).copyBinariesTo(
149 source.getBuiltBinaries(), series, pocket, archive, policy=policy)
150- # XXX cjwatson 2012-06-22 bug=231371: Copy custom uploads.
151
152 if binary_copies is not None:
153 copies.extend(binary_copies)
154+ binary_uploads = set(
155+ bpph.binarypackagerelease.build.package_upload
156+ for bpph in binary_copies)
157+ for binary_upload in binary_uploads:
158+ if binary_upload is not None:
159+ custom_files.extend(binary_upload.customfiles)
160+
161+ if custom_files:
162+ # Custom uploads aren't modelled as publication history records, so
163+ # we have to send these through the upload queue.
164+ custom_copier = CustomUploadsCopier(series, target_pocket=pocket)
165+ for custom in custom_files:
166+ custom_copier.copyUpload(custom)
167
168 # Always ensure the needed builds exist in the copy destination
169 # after copying the binaries.
170
171=== modified file 'lib/lp/soyuz/scripts/tests/test_copypackage.py'
172--- lib/lp/soyuz/scripts/tests/test_copypackage.py 2012-06-20 09:19:37 +0000
173+++ lib/lp/soyuz/scripts/tests/test_copypackage.py 2012-06-25 16:57:21 +0000
174@@ -111,11 +111,9 @@
175
176 Their filename, mimetype and file contents should be the same.
177 """
178- self.assertEquals(
179- old.filename, new.filename, 'Filename mismatch.')
180- self.assertEquals(
181- old.mimetype, new.mimetype, 'MIME type mismatch.')
182- self.assertEquals(old.read(), new.read(), 'Content mismatch.')
183+ self.assertEqual(old.filename, new.filename, 'Filename mismatch.')
184+ self.assertEqual(old.mimetype, new.mimetype, 'MIME type mismatch.')
185+ self.assertEqual(old.read(), new.read(), 'Content mismatch.')
186
187 def assertFileIsReset(self, reuploaded_file):
188 """Assert the given `ILibraryFileAlias` attributes were reset.
189@@ -123,10 +121,10 @@
190 The expiration date and the hits counter are reset and the
191 last access records was on file creation.
192 """
193- self.assertIs(reuploaded_file.expires, None)
194- self.assertEquals(
195+ self.assertIsNone(reuploaded_file.expires)
196+ self.assertEqual(
197 reuploaded_file.last_accessed, reuploaded_file.date_created)
198- self.assertEquals(reuploaded_file.hits, 0)
199+ self.assertEqual(reuploaded_file.hits, 0)
200
201 def testReUploadFileToTheSameContext(self):
202 # Re-uploading a librarian file to the same privacy/server
203@@ -139,7 +137,7 @@
204 transaction.commit()
205
206 self.assertIsNot(old, new)
207- self.assertEquals(
208+ self.assertEqual(
209 old.restricted, new.restricted, 'New file still private.')
210 self.assertSameContent(old, new)
211 self.assertFileIsReset(new)
212@@ -223,9 +221,8 @@
213
214 def assertNewFiles(self, new_files, result):
215 """Check new files created during update_files_privacy."""
216- self.assertEquals(
217- sorted([new_file.filename for new_file in new_files]),
218- result)
219+ self.assertEqual(
220+ sorted([new_file.filename for new_file in new_files]), result)
221
222 def _checkSourceFilesPrivacy(self, pub_record, restricted,
223 expected_n_files):
224@@ -233,20 +230,20 @@
225 n_files = 0
226 source = pub_record.sourcepackagerelease
227 for source_file in source.files:
228- self.assertEquals(
229+ self.assertEqual(
230 source_file.libraryfile.restricted, restricted,
231 'Privacy mismatch on %s' % source_file.libraryfile.filename)
232 n_files += 1
233- self.assertEquals(
234+ self.assertEqual(
235 source.upload_changesfile.restricted, restricted,
236 'Privacy mismatch on %s' % source.upload_changesfile.filename)
237 n_files += 1
238 for diff in source.package_diffs:
239- self.assertEquals(
240+ self.assertEqual(
241 diff.diff_content.restricted, restricted,
242 'Privacy mismatch on %s' % diff.diff_content.filename)
243 n_files += 1
244- self.assertEquals(
245+ self.assertEqual(
246 n_files, expected_n_files,
247 'Expected %d and got %d files' % (expected_n_files, n_files))
248
249@@ -335,20 +332,20 @@
250 n_files = 0
251 binary = pub_record.binarypackagerelease
252 for binary_file in binary.files:
253- self.assertEquals(
254+ self.assertEqual(
255 binary_file.libraryfile.restricted, restricted,
256 'Privacy mismatch on %s' % binary_file.libraryfile.filename)
257 n_files += 1
258 build = binary.build
259- self.assertEquals(
260+ self.assertEqual(
261 build.upload_changesfile.restricted, restricted,
262 'Privacy mismatch on %s' % build.upload_changesfile.filename)
263 n_files += 1
264- self.assertEquals(
265+ self.assertEqual(
266 build.log.restricted, restricted,
267 'Privacy mismatch on %s' % build.log.filename)
268 n_files += 1
269- self.assertEquals(
270+ self.assertEqual(
271 n_files, expected_n_files,
272 'Expected %d and got %d files' % (expected_n_files, n_files))
273
274@@ -464,18 +461,17 @@
275 """
276 copy_checker = CopyChecker(
277 self.archive, include_binaries=False, allow_delayed_copies=delayed)
278- self.assertIs(
279- None,
280+ self.assertIsNone(
281 copy_checker.checkCopy(
282 self.source, self.series, self.pocket,
283 check_permissions=False))
284 checked_copies = list(copy_checker.getCheckedCopies())
285- self.assertEquals(1, len(checked_copies))
286+ self.assertEqual(1, len(checked_copies))
287 [checked_copy] = checked_copies
288- self.assertEquals(
289+ self.assertEqual(
290 BuildSetStatus.NEEDSBUILD,
291 checked_copy.getStatusSummaryForBuilds()['status'])
292- self.assertEquals(delayed, checked_copy.delayed)
293+ self.assertEqual(delayed, checked_copy.delayed)
294
295 def assertCanCopyBinaries(self, delayed=False):
296 """Source and binary copy is allowed.
297@@ -493,18 +489,17 @@
298 """
299 copy_checker = CopyChecker(
300 self.archive, include_binaries=True, allow_delayed_copies=delayed)
301- self.assertIs(
302- None,
303+ self.assertIsNone(
304 copy_checker.checkCopy(
305 self.source, self.series, self.pocket,
306 check_permissions=False))
307 checked_copies = list(copy_checker.getCheckedCopies())
308- self.assertEquals(1, len(checked_copies))
309+ self.assertEqual(1, len(checked_copies))
310 [checked_copy] = checked_copies
311 self.assertTrue(
312 checked_copy.getStatusSummaryForBuilds()['status'] >=
313 BuildSetStatus.FULLYBUILT_PENDING)
314- self.assertEquals(delayed, checked_copy.delayed)
315+ self.assertEqual(delayed, checked_copy.delayed)
316
317 def assertCannotCopySourceOnly(self, msg, person=None,
318 check_permissions=False):
319@@ -518,7 +513,7 @@
320 copy_checker.checkCopy, self.source, self.series, self.pocket,
321 person, check_permissions)
322 checked_copies = list(copy_checker.getCheckedCopies())
323- self.assertEquals(0, len(checked_copies))
324+ self.assertEqual(0, len(checked_copies))
325
326 def assertCannotCopyBinaries(self, msg):
327 """`CopyChecker.checkCopy()` including binaries raises CannotCopy.
328@@ -531,7 +526,7 @@
329 copy_checker.checkCopy, self.source, self.series, self.pocket,
330 None, False)
331 checked_copies = list(copy_checker.getCheckedCopies())
332- self.assertEquals(0, len(checked_copies))
333+ self.assertEqual(0, len(checked_copies))
334
335 def test_cannot_copy_binaries_from_building(self):
336 [build] = self.source.createMissingBuilds()
337@@ -609,13 +604,12 @@
338 with StormStatementRecorder() as recorder:
339 copy_checker = CopyChecker(self.archive, include_binaries=False)
340 for source in sources:
341- self.assertIs(
342- None,
343+ self.assertIsNone(
344 copy_checker.checkCopy(
345 source, self.series, self.pocket, person=person,
346 check_permissions=check_permissions))
347 checked_copies = list(copy_checker.getCheckedCopies())
348- self.assertEquals(nb_of_sources, len(checked_copies))
349+ self.assertEqual(nb_of_sources, len(checked_copies))
350 return recorder
351
352 def test_queries_copy_check(self):
353@@ -834,13 +828,11 @@
354
355 # At this point copy is allowed with or without binaries.
356 copy_checker = CopyChecker(archive, include_binaries=False)
357- self.assertIs(
358- None,
359+ self.assertIsNone(
360 copy_checker.checkCopy(
361 source, series, pocket, check_permissions=False))
362 copy_checker = CopyChecker(archive, include_binaries=True)
363- self.assertIs(
364- None,
365+ self.assertIsNone(
366 copy_checker.checkCopy(
367 source, series, pocket, check_permissions=False))
368
369@@ -852,8 +844,8 @@
370
371 # Now source-only copies are allowed.
372 copy_checker = CopyChecker(archive, include_binaries=False)
373- self.assertIs(
374- None, copy_checker.checkCopy(
375+ self.assertIsNone(
376+ copy_checker.checkCopy(
377 source, series, pocket, check_permissions=False))
378
379 # Copies with binaries are denied.
380@@ -990,8 +982,7 @@
381
382 # The first source-only copy is allowed, thus stored in the
383 # copy checker inventory.
384- self.assertIs(
385- None,
386+ self.assertIsNone(
387 copy_checker.checkCopy(
388 source, source.distroseries, source.pocket,
389 check_permissions=False))
390@@ -1164,7 +1155,7 @@
391 self.test_publisher.prepareBreezyAutotest()
392
393 def assertCopied(self, copies, series, arch_tags):
394- self.assertEquals(
395+ self.assertEqual(
396 [u'foo 666 in %s' % series.name] +
397 [u'foo-bin 666 in %s %s' % (series.name, arch_tag)
398 for arch_tag in arch_tags],
399@@ -1268,9 +1259,9 @@
400
401 def assertComponentSectionAndPriority(self, component, source,
402 destination):
403- self.assertEquals(component, destination.component)
404- self.assertEquals(source.section, destination.section)
405- self.assertEquals(source.priority, destination.priority)
406+ self.assertEqual(component, destination.component)
407+ self.assertEqual(source.section, destination.section)
408+ self.assertEqual(source.priority, destination.priority)
409
410 def test_new_publication_overrides(self):
411 # When we copy publications, if the destination primary archive has
412@@ -1297,7 +1288,7 @@
413 [copied_source, copied_bin_i386, copied_bin_hppa] = self.doCopy(
414 source, target_archive, nobby, source.pocket, True)
415 universe = getUtility(IComponentSet)['universe']
416- self.assertEquals(universe, copied_source.component)
417+ self.assertEqual(universe, copied_source.component)
418 self.assertComponentSectionAndPriority(
419 universe, bin_i386, copied_bin_i386)
420 self.assertComponentSectionAndPriority(
421@@ -1333,7 +1324,7 @@
422
423 [copied_source, copied_bin_i386, copied_bin_hppa] = self.doCopy(
424 source, target_archive, nobby, source.pocket, True)
425- self.assertEquals(copied_source.component, existing_source.component)
426+ self.assertEqual(copied_source.component, existing_source.component)
427 self.assertComponentSectionAndPriority(
428 ebin_i386.component, ebin_i386, copied_bin_i386)
429 self.assertComponentSectionAndPriority(
430@@ -1365,7 +1356,7 @@
431
432 [copied_source, copied_bin_i386, copied_bin_hppa] = self.doCopy(
433 source, archive, nobby, PackagePublishingPocket.UPDATES, True)
434- self.assertEquals(copied_source.component, source.component)
435+ self.assertEqual(copied_source.component, source.component)
436 self.assertComponentSectionAndPriority(
437 bin_i386.component, bin_i386, copied_bin_i386)
438 self.assertComponentSectionAndPriority(
439@@ -1387,7 +1378,7 @@
440 [copied_source, copied_bin_i386, copied_bin_hppa] = self.doCopy(
441 source, target_archive, nobby, source.pocket, True)
442 main = getUtility(IComponentSet)['main']
443- self.assertEquals(main, copied_source.component)
444+ self.assertEqual(main, copied_source.component)
445 self.assertComponentSectionAndPriority(
446 main, bin_i386, copied_bin_i386)
447 self.assertComponentSectionAndPriority(
448@@ -1444,9 +1435,8 @@
449 person=target_archive.owner, check_permissions=False,
450 send_email=True)
451 [notification] = pop_notifications()
452- self.assertEquals(
453- get_ppa_reference(target_archive),
454- notification['X-Launchpad-PPA'])
455+ self.assertEqual(
456+ get_ppa_reference(target_archive), notification['X-Launchpad-PPA'])
457 body = notification.get_payload()[0].get_payload()
458 expected = dedent("""\
459 Accepted:
460@@ -1483,11 +1473,10 @@
461 person=source.sourcepackagerelease.creator,
462 check_permissions=False, send_email=True)
463 [notification, announcement] = pop_notifications()
464- self.assertEquals(
465- 'Foo Bar <foo.bar@canonical.com>', notification['To'])
466- self.assertEquals('nobby-changes@example.com', announcement['To'])
467+ self.assertEqual('Foo Bar <foo.bar@canonical.com>', notification['To'])
468+ self.assertEqual('nobby-changes@example.com', announcement['To'])
469 for mail in (notification, announcement):
470- self.assertEquals(
471+ self.assertEqual(
472 '[ubuntutest/nobby] foo 1.0-2 (Accepted)', mail['Subject'])
473 expected_text = dedent("""\
474 foo (1.0-2) unstable; urgency=3Dlow
475@@ -1523,7 +1512,7 @@
476 check_permissions=False, send_email=True,
477 sponsored=sponsored_person)
478 [notification, announcement] = pop_notifications()
479- self.assertEquals(
480+ self.assertEqual(
481 'Sponsored <sponsored@example.com>', announcement['From'])
482 self.assertEqual(sponsored_person, copied_source.creator)
483
484@@ -1602,11 +1591,9 @@
485 notifications = pop_notifications()
486 self.assertEqual(1, len(notifications))
487 [notification] = notifications
488- self.assertEquals(
489- 'Foo Bar <foo.bar@canonical.com>', notification['To'])
490- self.assertEquals(
491- '[ubuntutest/nobby] foo 1.0-2 (Rejected)',
492- notification['Subject'])
493+ self.assertEqual('Foo Bar <foo.bar@canonical.com>', notification['To'])
494+ self.assertEqual(
495+ '[ubuntutest/nobby] foo 1.0-2 (Rejected)', notification['Subject'])
496 expected_text = (
497 "Rejected:\n"
498 "foo 1.0-2 in breezy-autotest (a different source with the same "
499@@ -1623,7 +1610,7 @@
500 [source], target_archive, nobby, source.pocket, False,
501 person=target_archive.owner, check_permissions=False,
502 send_email=False)
503- self.assertEquals([], pop_notifications())
504+ self.assertEqual([], pop_notifications())
505
506 def test_copying_unsupported_arch_with_override(self):
507 # When the copier is passed an unsupported arch with an override
508@@ -1659,9 +1646,7 @@
509 person=target_archive.owner, check_permissions=False,
510 send_email=False)
511
512- self.assertEqual(
513- target_archive.owner,
514- copied_source.creator)
515+ self.assertEqual(target_archive.owner, copied_source.creator)
516
517 def test_unsponsored_copy_does_not_set_sponsor(self):
518 # If the copy is not sponsored, SPPH.sponsor is none
519@@ -1673,9 +1658,42 @@
520 person=target_archive.owner, check_permissions=False,
521 send_email=False)
522
523+ self.assertIsNone(copied_source.sponsor)
524+
525+ def test_copy_custom_upload_files(self):
526+ # Custom upload files are queued for republication when they are
527+ # copied.
528+ self.test_publisher.breezy_autotest.status = SeriesStatus.CURRENT
529+ source = self.test_publisher.getPubSource(
530+ pocket=PackagePublishingPocket.PROPOSED)
531+ self.test_publisher.getPubBinaries(
532+ pocket=PackagePublishingPocket.PROPOSED, pub_source=source)
533+ [build] = source.getBuilds()
534+ custom_file = self.factory.makeLibraryFileAlias()
535+ build.package_upload.addCustom(
536+ custom_file, PackageUploadCustomFormat.DIST_UPGRADER)
537+ # Make the new librarian file available.
538+ self.layer.txn.commit()
539+
540+ self.doCopy(
541+ source, source.archive, source.distroseries,
542+ PackagePublishingPocket.UPDATES, include_binaries=True)
543+ [upload] = source.distroseries.getPackageUploads(
544+ status=PackageUploadStatus.ACCEPTED, archive=source.archive,
545+ pocket=PackagePublishingPocket.UPDATES,
546+ custom_type=PackageUploadCustomFormat.DIST_UPGRADER)
547+
548+ # The upload is targeted to the right publishing context.
549+ self.assertEqual(source.archive, upload.archive)
550+ self.assertEqual(source.distroseries, upload.distroseries)
551+ self.assertEqual(PackagePublishingPocket.UPDATES, upload.pocket)
552+
553+ # It contains only the custom files.
554+ self.assertEqual([], list(upload.sources))
555+ self.assertEqual([], list(upload.builds))
556 self.assertEqual(
557- copied_source.sponsor,
558- None)
559+ [custom_file],
560+ [custom.libraryfilealias for custom in upload.customfiles])
561
562
563 class TestDoDelayedCopy(TestCaseWithFactory, BaseDoCopyTests):
564@@ -1696,16 +1714,13 @@
565
566 # Make ubuntutest/breezy-autotest CURRENT so uploads to SECURITY
567 # pocket can be accepted.
568- self.test_publisher.breezy_autotest.status = (
569- SeriesStatus.CURRENT)
570+ self.test_publisher.breezy_autotest.status = SeriesStatus.CURRENT
571
572 def assertCopied(self, copy, series, arch_tags):
573- self.assertEquals(
574- copy.sources[0].sourcepackagerelease.title,
575- 'foo - 666')
576- self.assertEquals(
577- sorted(arch_tags),
578- sorted([pub.build.arch_tag for pub in copy.builds]))
579+ self.assertEqual(
580+ copy.sources[0].sourcepackagerelease.title, 'foo - 666')
581+ self.assertContentEqual(
582+ arch_tags, [pub.build.arch_tag for pub in copy.builds])
583
584 def doCopy(self, source, archive, series, pocket, include_binaries):
585 return _do_delayed_copy(source, archive, series, pocket, True)
586@@ -1758,35 +1773,32 @@
587
588 # A delayed-copy `IPackageUpload` record is returned.
589 self.assertTrue(delayed_copy.is_delayed_copy)
590- self.assertEquals(
591- PackageUploadStatus.ACCEPTED, delayed_copy.status)
592+ self.assertEqual(PackageUploadStatus.ACCEPTED, delayed_copy.status)
593
594 # The returned object has a more descriptive 'displayname'
595 # attribute than plain `IPackageUpload` instances.
596- self.assertEquals(
597+ self.assertEqual(
598 'Delayed copy of foocomm - '
599 '1.0-2 (source, i386, raw-dist-upgrader)',
600 delayed_copy.displayname)
601
602 # It is targeted to the right publishing context.
603- self.assertEquals(self.copy_archive, delayed_copy.archive)
604- self.assertEquals(self.copy_series, delayed_copy.distroseries)
605- self.assertEquals(self.copy_pocket, delayed_copy.pocket)
606+ self.assertEqual(self.copy_archive, delayed_copy.archive)
607+ self.assertEqual(self.copy_series, delayed_copy.distroseries)
608+ self.assertEqual(self.copy_pocket, delayed_copy.pocket)
609
610 # And it contains the source, build and custom files.
611- self.assertEquals(
612+ self.assertEqual(
613 [source.sourcepackagerelease],
614 [pus.sourcepackagerelease for pus in delayed_copy.sources])
615
616 [build] = source.getBuilds()
617- self.assertEquals(
618- [build],
619- [pub.build for pub in delayed_copy.builds])
620+ self.assertEqual([build], [pub.build for pub in delayed_copy.builds])
621
622 [custom_file] = [
623 custom.libraryfilealias
624 for custom in build.package_upload.customfiles]
625- self.assertEquals(
626+ self.assertEqual(
627 [custom_file],
628 [custom.libraryfilealias for custom in delayed_copy.customfiles])
629
630@@ -1843,23 +1855,20 @@
631 # Setup and execute the delayed copy procedure. This should
632 # now result in an accepted delayed upload.
633 delayed_copy = self.do_delayed_copy(source)
634- self.assertEquals(
635- PackageUploadStatus.ACCEPTED, delayed_copy.status)
636+ self.assertEqual(PackageUploadStatus.ACCEPTED, delayed_copy.status)
637
638 # And it contains the source, build and custom files.
639- self.assertEquals(
640+ self.assertEqual(
641 [source.sourcepackagerelease],
642 [pus.sourcepackagerelease for pus in delayed_copy.sources])
643
644 [build] = source.getBuilds()
645- self.assertEquals(
646- [build],
647- [pub.build for pub in delayed_copy.builds])
648+ self.assertEqual([build], [pub.build for pub in delayed_copy.builds])
649
650 [custom_file] = [
651 custom.libraryfilealias
652 for custom in build.package_upload.customfiles]
653- self.assertEquals(
654+ self.assertEqual(
655 [custom_file],
656 [custom.libraryfilealias for custom in delayed_copy.customfiles])
657
658@@ -1915,9 +1924,8 @@
659 # archive context. Also after this point, the same delayed-copy
660 # request will be denied by `CopyChecker`.
661 [build_hppa, build_i386] = source.getBuilds()
662- self.assertEquals(
663- [build_i386],
664- [pub.build for pub in delayed_copy.builds])
665+ self.assertEqual(
666+ [build_i386], [pub.build for pub in delayed_copy.builds])
667
668
669 class CopyPackageScriptTestCase(unittest.TestCase):
670@@ -2068,8 +2076,7 @@
671 self.assertEqual(len(copied), size)
672
673 for candidate in copied:
674- self.assertEqual(
675- candidate.status, PackagePublishingStatus.PENDING)
676+ self.assertEqual(PackagePublishingStatus.PENDING, candidate.status)
677
678 def excludeOlds(found, old_pending_ids):
679 return [pub.id for pub in found if pub.id not in old_pending_ids]
680@@ -2399,16 +2406,14 @@
681 active_warty_architectures = [
682 arch.architecturetag for arch in warty.architectures
683 if arch.getChroot()]
684- self.assertEqual(
685- active_warty_architectures, ['i386'])
686+ self.assertEqual(active_warty_architectures, ['i386'])
687
688 # Setup ubuntu/hoary supporting i386 and hppa architetures.
689 hoary = ubuntu.getSeries('hoary')
690 test_publisher.addFakeChroots(hoary)
691 active_hoary_architectures = [
692 arch.architecturetag for arch in hoary.architectures]
693- self.assertEqual(
694- sorted(active_hoary_architectures), ['hppa', 'i386'])
695+ self.assertEqual(sorted(active_hoary_architectures), ['hppa', 'i386'])
696
697 # We will create an architecture-specific source and its binaries
698 # for i386 in ubuntu/warty. They will be copied over.
699@@ -2500,8 +2505,7 @@
700 # architecture (hppa).
701 [new_build] = copied_source.getBuilds()
702 self.assertEqual(
703- new_build.title,
704- 'hppa build of boing 1.0 in ubuntu hoary RELEASE')
705+ new_build.title, 'hppa build of boing 1.0 in ubuntu hoary RELEASE')
706
707 def testVersionConflictInDifferentPockets(self):
708 """Copy-package stops copies conflicting in different pocket.
709@@ -2626,8 +2630,7 @@
710
711 [copied_source, original_source] = sources
712
713- self.assertEqual(
714- copied_source.pocket, PackagePublishingPocket.UPDATES)
715+ self.assertEqual(copied_source.pocket, PackagePublishingPocket.UPDATES)
716 self.assertEqual(
717 original_source.pocket, PackagePublishingPocket.SECURITY)
718
719@@ -2673,8 +2676,7 @@
720 copied = copy_helper.mainTask()
721
722 [source_copy, i386_copy] = copied
723- self.assertEqual(
724- source_copy.displayname, 'lazy-building 1.0 in hoary')
725+ self.assertEqual(source_copy.displayname, 'lazy-building 1.0 in hoary')
726 self.assertEqual(i386_copy.displayname, 'lazy-bin 1.0 in hoary i386')
727
728 target_archive = copy_helper.destination.archive
729@@ -2735,12 +2737,8 @@
730 from_suite='warty', to_suite='hoary', to_ppa='mark')
731 copied = copy_helper.mainTask()
732
733- self.assertEqual(
734- str(copy_helper.location),
735- 'cprov: warty-RELEASE')
736- self.assertEqual(
737- str(copy_helper.destination),
738- 'mark: hoary-RELEASE')
739+ self.assertEqual('cprov: warty-RELEASE', str(copy_helper.location))
740+ self.assertEqual('mark: hoary-RELEASE', str(copy_helper.destination))
741
742 target_archive = copy_helper.destination.archive
743 self.checkCopies(copied, target_archive, 2)
744@@ -3267,10 +3265,10 @@
745 section='misc')
746
747 checker = CopyChecker(warty.main_archive, include_binaries=False)
748- self.assertIs(
749- None,
750- checker.checkCopy(proposed_source, warty,
751- PackagePublishingPocket.UPDATES, check_permissions=False))
752+ self.assertIsNone(
753+ checker.checkCopy(
754+ proposed_source, warty, PackagePublishingPocket.UPDATES,
755+ check_permissions=False))
756
757 def testCopySourceWithConflictingFilesInPPAs(self):
758 """We can copy source if the source files match, both in name and
759@@ -3355,10 +3353,10 @@
760 self.layer.txn.commit()
761
762 checker = CopyChecker(dest_ppa, include_binaries=False)
763- self.assertIs(
764- None,
765- checker.checkCopy(test2_source, warty,
766- PackagePublishingPocket.RELEASE, check_permissions=False))
767+ self.assertIsNone(
768+ checker.checkCopy(
769+ test2_source, warty, PackagePublishingPocket.RELEASE,
770+ check_permissions=False))
771
772 def testCopySourceWithExpiredSourcesInDestination(self):
773 """We can also copy sources if the destination archive has expired
774@@ -3401,7 +3399,7 @@
775 switch_dbuser(self.dbuser)
776
777 checker = CopyChecker(dest_ppa, include_binaries=False)
778- self.assertIs(
779- None,
780- checker.checkCopy(test2_source, warty,
781- PackagePublishingPocket.RELEASE, check_permissions=False))
782+ self.assertIsNone(
783+ checker.checkCopy(
784+ test2_source, warty, PackagePublishingPocket.RELEASE,
785+ check_permissions=False))
786
787=== modified file 'lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py'
788--- lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2012-05-30 10:25:43 +0000
789+++ lib/lp/soyuz/scripts/tests/test_custom_uploads_copier.py 2012-06-25 16:57:21 +0000
790@@ -6,6 +6,7 @@
791 __metaclass__ = type
792
793 from lp.registry.interfaces.pocket import PackagePublishingPocket
794+from lp.registry.interfaces.series import SeriesStatus
795 from lp.soyuz.enums import (
796 ArchivePurpose,
797 PackageUploadCustomFormat,
798@@ -108,7 +109,7 @@
799 # Alas, PackageUploadCustom relies on the Librarian.
800 layer = LaunchpadZopelessLayer
801
802- def makeUpload(self, distroseries=None,
803+ def makeUpload(self, distroseries=None, pocket=None,
804 custom_type=PackageUploadCustomFormat.DEBIAN_INSTALLER,
805 version=None, arch=None):
806 """Create a `PackageUploadCustom`."""
807@@ -121,7 +122,7 @@
808 arch = self.factory.getUniqueString()
809 filename = "%s.tar.gz" % '_'.join([package_name, version, arch])
810 package_upload = self.factory.makeCustomPackageUpload(
811- distroseries=distroseries, custom_type=custom_type,
812+ distroseries=distroseries, pocket=pocket, custom_type=custom_type,
813 filename=filename)
814 return package_upload.customfiles[0]
815
816@@ -213,6 +214,19 @@
817 upload.id for upload in copier.getCandidateUploads(source_series)]
818 self.assertEqual(sorted(candidate_ids, reverse=True), candidate_ids)
819
820+ def test_getCandidateUploads_filters_by_pocket(self):
821+ # getCandidateUploads ignores uploads for other pockets.
822+ source_series = self.factory.makeDistroSeries()
823+ matching_upload = self.makeUpload(
824+ source_series, pocket=PackagePublishingPocket.PROPOSED)
825+ nonmatching_upload = self.makeUpload(
826+ source_series, pocket=PackagePublishingPocket.BACKPORTS)
827+ copier = CustomUploadsCopier(FakeDistroSeries())
828+ candidate_uploads = copier.getCandidateUploads(
829+ source_series, PackagePublishingPocket.PROPOSED)
830+ self.assertContentEqual([matching_upload], candidate_uploads)
831+ self.assertNotIn(nonmatching_upload, candidate_uploads)
832+
833 def test_getKey_includes_format_and_architecture(self):
834 # The key returned by getKey consists of custom upload type,
835 # and architecture.
836@@ -221,7 +235,7 @@
837 # component name as well.
838 source_series = self.factory.makeDistroSeries()
839 upload = self.makeUpload(
840- source_series, PackageUploadCustomFormat.DIST_UPGRADER,
841+ source_series, custom_type=PackageUploadCustomFormat.DIST_UPGRADER,
842 arch='mips')
843 copier = CustomUploadsCopier(FakeDistroSeries())
844 expected_key = (
845@@ -379,8 +393,8 @@
846 # copyUpload copies the original upload into the release pocket,
847 # even though the original is more likely to be in another
848 # pocket.
849- original_upload = self.makeUpload()
850- original_upload.packageupload.pocket = PackagePublishingPocket.UPDATES
851+ original_upload = self.makeUpload(
852+ pocket=PackagePublishingPocket.UPDATES)
853 target_series = self.factory.makeDistroSeries()
854 copier = CustomUploadsCopier(target_series)
855 copied_upload = copier.copyUpload(original_upload)
856@@ -388,6 +402,21 @@
857 PackagePublishingPocket.RELEASE,
858 copied_upload.packageupload.pocket)
859
860+ def test_copyUpload_to_updates_pocket(self):
861+ # copyUpload copies an upload between pockets in the same series if
862+ # requested.
863+ source_series = self.factory.makeDistroSeries(
864+ status=SeriesStatus.CURRENT)
865+ original_upload = self.makeUpload(
866+ distroseries=source_series,
867+ pocket=PackagePublishingPocket.PROPOSED)
868+ copier = CustomUploadsCopier(
869+ source_series, target_pocket=PackagePublishingPocket.UPDATES)
870+ copied_upload = copier.copyUpload(original_upload)
871+ self.assertEqual(
872+ PackagePublishingPocket.UPDATES,
873+ copied_upload.packageupload.pocket)
874+
875 def test_copyUpload_accepts_upload(self):
876 # Uploads created by copyUpload are automatically accepted.
877 original_upload = self.makeUpload()
878
879=== modified file 'lib/lp/testing/factory.py'
880--- lib/lp/testing/factory.py 2012-06-11 09:55:13 +0000
881+++ lib/lp/testing/factory.py 2012-06-25 16:57:21 +0000
882@@ -3493,28 +3493,32 @@
883 sourcepackagename=sourcepackagename, component=component))
884 return upload
885
886- def makeBuildPackageUpload(self, distroseries=None,
887- binarypackagename=None):
888+ def makeBuildPackageUpload(self, distroseries=None, pocket=None,
889+ binarypackagename=None,
890+ source_package_release=None):
891 """Make a `PackageUpload` with a `PackageUploadBuild` attached."""
892 if distroseries is None:
893 distroseries = self.makeDistroSeries()
894 upload = self.makePackageUpload(
895- distroseries=distroseries, archive=distroseries.main_archive)
896- build = self.makeBinaryPackageBuild()
897+ distroseries=distroseries, archive=distroseries.main_archive,
898+ pocket=pocket)
899+ build = self.makeBinaryPackageBuild(
900+ source_package_release=source_package_release, pocket=pocket)
901 upload.addBuild(build)
902 self.makeBinaryPackageRelease(
903 binarypackagename=binarypackagename, build=build)
904 return upload
905
906- def makeCustomPackageUpload(self, distroseries=None, custom_type=None,
907- filename=None):
908+ def makeCustomPackageUpload(self, distroseries=None, pocket=None,
909+ custom_type=None, filename=None):
910 """Make a `PackageUpload` with a `PackageUploadCustom` attached."""
911 if distroseries is None:
912 distroseries = self.makeDistroSeries()
913 if custom_type is None:
914 custom_type = PackageUploadCustomFormat.DEBIAN_INSTALLER
915 upload = self.makePackageUpload(
916- distroseries=distroseries, archive=distroseries.main_archive)
917+ distroseries=distroseries, archive=distroseries.main_archive,
918+ pocket=pocket)
919 file_alias = self.makeLibraryFileAlias(filename=filename)
920 upload.addCustom(file_alias, custom_type)
921 return upload
922
923=== modified file 'lib/lp/testing/tests/test_factory.py'
924--- lib/lp/testing/tests/test_factory.py 2012-01-01 02:58:52 +0000
925+++ lib/lp/testing/tests/test_factory.py 2012-06-25 16:57:21 +0000
926@@ -1,4 +1,4 @@
927-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
928+# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
929 # GNU Affero General Public License version 3 (see the file LICENSE).
930
931 """Tests for the Launchpad object factory."""
932@@ -781,10 +781,12 @@
933 distroseries = self.factory.makeDistroSeries()
934 bpn = self.factory.makeBinaryPackageName()
935 pu = self.factory.makeBuildPackageUpload(
936- distroseries=distroseries, binarypackagename=bpn)
937+ distroseries=distroseries, pocket=PackagePublishingPocket.PROPOSED,
938+ binarypackagename=bpn)
939 build = list(pu.builds)[0].build
940 self.assertEqual(distroseries, pu.distroseries)
941 self.assertEqual(distroseries.distribution, pu.archive.distribution)
942+ self.assertEqual(PackagePublishingPocket.PROPOSED, pu.pocket)
943 release = IStore(distroseries).find(
944 BinaryPackageRelease, BinaryPackageRelease.build == build).one()
945 self.assertEqual(bpn, release.binarypackagename)
946@@ -803,11 +805,12 @@
947 custom_type = PackageUploadCustomFormat.ROSETTA_TRANSLATIONS
948 filename = self.factory.getUniqueString()
949 pu = self.factory.makeCustomPackageUpload(
950- distroseries=distroseries, custom_type=custom_type,
951- filename=filename)
952+ distroseries=distroseries, pocket=PackagePublishingPocket.PROPOSED,
953+ custom_type=custom_type, filename=filename)
954 custom = list(pu.customfiles)[0]
955 self.assertEqual(distroseries, pu.distroseries)
956 self.assertEqual(distroseries.distribution, pu.archive.distribution)
957+ self.assertEqual(PackagePublishingPocket.PROPOSED, pu.pocket)
958 self.assertEqual(custom_type, custom.customformat)
959 self.assertEqual(filename, custom.libraryfilealias.filename)
960