Merge lp:~michael.nelson/launchpad/510331-syncsources-latest-pub into lp:launchpad

Proposed by Michael Nelson
Status: Merged
Approved by: Michael Nelson
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~michael.nelson/launchpad/510331-syncsources-latest-pub
Merge into: lp:launchpad
Diff against target: 190 lines (+90/-22)
5 files modified
lib/lp/soyuz/doc/archive.txt (+11/-7)
lib/lp/soyuz/interfaces/archive.py (+2/-2)
lib/lp/soyuz/model/archive.py (+24/-11)
lib/lp/soyuz/stories/webservice/xx-archive.txt (+9/-2)
lib/lp/soyuz/tests/test_archive.py (+44/-0)
To merge this branch: bzr merge lp:~michael.nelson/launchpad/510331-syncsources-latest-pub
Reviewer Review Type Date Requested Status
Graham Binns (community) code Approve
Review via email: mp+18070@code.launchpad.net

Commit message

Ensures that syncSources only chooses the latest *PUBLISHED* sources, rather than simply the latest sources.

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

= Summary =
Fixes 510331 by ensuring that syncSources only finds pub records that are currently PUBLISHED.

== Proposed fix ==

== Pre-implementation notes ==

See note on the bug, as well as:
<noodles> ok, sounds good. Also with bug 510331
<bigjools> noodles: yeah that one's easy
<noodles> IArchive.syncSource() already allows you to specify the version... but with the solution you've suggested for syncSources()
<bigjools> ?
<noodles> it would mean that we'd not copy deleted sources even when there is no published one... is that expected?
<bigjools> yes, I think so
<noodles> OK. easy then.
<bigjools> well, would you expect it to?
<noodles> I'm imagining using the web ui, looking at a bunch of deleted sources and selecting them...
<bigjools> that's syncSource behaviour really
<bigjools> syncSources is more automatic
<bigjools> but we can make it explicit in the docstring
<noodles> OK.

== Implementation details ==

I wasn't sure of the best way to test the private helper method. I ended up going with removeSecurityProxy() for the test, but would be happy to hear about a neater solution?

== Tests ==
bin/test -vvt doc/archive.txt -t TestCollectLatestPublishedSources

== Demo and Q/A ==

We can Q/A this using the API on dogfood (or edge).

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/soyuz/doc/archive.txt
  lib/lp/soyuz/tests/test_archive.py
  lib/lp/soyuz/model/archive.py

== Pylint notices ==

lib/lp/soyuz/model/archive.py
    1099: [C0301] Line too long (81/78) -- fixed.
    14: [F0401] Unable to import 'lazr.lifecycle.event' (No module named lifecycle)

Revision history for this message
Graham Binns (gmb) wrote :

Hi Michael,

This looks good to me. One minor nitpick, but otherwise it's fine to
land as-is.

> === modified file 'lib/lp/soyuz/model/archive.py'
> --- lib/lp/soyuz/model/archive.py 2010-01-12 08:31:42 +0000
> +++ lib/lp/soyuz/model/archive.py 2010-01-26 11:54:18 +0000
> @@ -1121,6 +1112,23 @@
>
> self._copySources([source], to_pocket, to_series, include_binaries)
>
> + def _collectLatestPublishedSources(self, from_archive, source_names):
> + """Private helper to collect the latest published sources for an
> + archive.
> + """
> + sources = []
> + for name in source_names:
> + # Check to see if the source package exists, and raise a useful
> + # error if it doesn't.

What qualifies as "A useful error" here? It'd be nice to say in the
comment - or better still in the docstring - "will raise a FooBarError
if the source package doesn't exist."

> + getUtility(ISourcePackageNameSet)[name]
> + # Grabbing the item at index 0 ensures it's the most recent
> + # publication.
> + sources.append(
> + from_archive.getPublishedSources(
> + name=name, exact_match=True,
> + status=PackagePublishingStatus.PUBLISHED)[0])
> + return sources
> +
> def _copySources(self, sources, to_pocket, to_series=None,
> include_binaries=False):
> """Private helper function to copy sources to this archive.
>

review: Approve (code)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

<noodles775> gmb: great. Did you have any thoughts about a better way to test the private method (as mentioned in the MP)?
<gmb> noodles775: Argh, must've skipped that bit of the mp. Sorry. Hang on...
<gmb> noodles775: So, no, I can't think of a better way (which is why I never balked at the use of rSP()). The only way I could think of is to test it indirectly, but I'd rather have the direct test myself.
<noodles775> Great, thanks for that.

Revision history for this message
Michael Nelson (michael.nelson) wrote :

Hi gmb,

I just got back to this branch, tested it, and had errors or the webservice branch that tested syncSource(s) through the API. The error was simply because the sources used in the test weren't in the published status, but it was 500ing, rather than a nice API error, so I've updated the code to avoid that also.

Here's the diff:

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-01-28 16:54:43 +0000
+++ lib/lp/soyuz/model/archive.py 2010-02-02 11:28:11 +0000
@@ -1127,10 +1127,11 @@
             getUtility(ISourcePackageNameSet)[name]
             # Grabbing the item at index 0 ensures it's the most recent
             # publication.
- sources.append(
- from_archive.getPublishedSources(
- name=name, exact_match=True,
- status=PackagePublishingStatus.PUBLISHED)[0])
+ published_sources = from_archive.getPublishedSources(
+ name=name, exact_match=True,
+ status=PackagePublishingStatus.PUBLISHED)
+ if published_sources.count() > 0:
+ sources.append(published_sources[0])
         return sources

     def _copySources(self, sources, to_pocket, to_series=None,

=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2009-10-07 08:28:03 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-02-02 11:57:30 +0000
@@ -576,9 +576,12 @@
     ... sourcename="package1", version="1.0",
     ... archive=ubuntu_db.main_archive)

+ >>> from lp.soyuz.interfaces.publishing import (
+ ... PackagePublishingStatus)
     >>> ignore = test_publisher.getPubSource(
     ... sourcename="package1", version="1.1",
- ... archive=ubuntu_db.main_archive)
+ ... archive=ubuntu_db.main_archive,
+ ... status=PackagePublishingStatus.PUBLISHED)

     >>> ignore = test_publisher.getPubSource(
     ... sourcename="package2", version="1.0",
@@ -742,6 +745,8 @@

     >>> private_publication = test_publisher.createSource(
     ... cprov.archive, 'foocomm', '1.0-1')
+ >>> private_publication.secure_record.status = (
+ ... PackagePublishingStatus.PUBLISHED)

     >>> logout()

@@ -908,8 +913,10 @@
 version.

     >>> login('<email address hidden>')
- >>> unused = test_publisher.createSource(
+ >>> subsequent_version = test_publisher.createSource(
     ... cprov.archive, 'foocomm', '1.0-2')
+ >>> subsequent_version.secure_record.status = (
+ ... PackagePublishingStatus.PUBLISHED)
     >>> logout()

     >>> print cprov_webservice.named_post(

Revision history for this message
Graham Binns (gmb) wrote :

As discussed on IRC, you should test for `not foo.is_empty()` rather than `foo.count() > 0` as it's more efficient. Other than that, this change looks fine.

review: Approve (code)
Revision history for this message
Michael Nelson (michael.nelson) wrote :

<noodles775> gmb, jtv: http://pastebin.ubuntu.com/367524/
<noodles775> Looks like is_empty isn't included on the ISQLObjectResultSet in storm.zope.interfaces :/
<gmb> ?!
<gmb> Oh, ah.
<gmb> I see.
<gmb> That's suboptimal.
<gmb> noodles775: In that case, just go back to using .count(). You could maybe file a tech-debt bug about changing getPublishedSources() to use Storm, but I don't know how worthwhile that would be.
<noodles775> Yep, thanks.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt 2010-01-12 08:59:23 +0000
+++ lib/lp/soyuz/doc/archive.txt 2010-02-02 12:06:20 +0000
@@ -2349,13 +2349,17 @@
2349 >>> test_publisher.addFakeChroots(hoary)2349 >>> test_publisher.addFakeChroots(hoary)
2350 >>> unused = test_publisher.setUpDefaultDistroSeries(hoary)2350 >>> unused = test_publisher.setUpDefaultDistroSeries(hoary)
2351 >>> discard = test_publisher.getPubSource(2351 >>> discard = test_publisher.getPubSource(
2352 ... sourcename="package1", version="1.0", archive=cprov.archive)2352 ... sourcename="package1", version="1.0", archive=cprov.archive,
2353 >>> discard = test_publisher.getPubSource(2353 ... status=PackagePublishingStatus.PUBLISHED)
2354 ... sourcename="package1", version="1.1", archive=cprov.archive)2354 >>> discard = test_publisher.getPubSource(
2355 >>> discard = test_publisher.getPubSource(2355 ... sourcename="package1", version="1.1", archive=cprov.archive,
2356 ... sourcename="package2", version="1.0", archive=cprov.archive)2356 ... status=PackagePublishingStatus.PUBLISHED)
2357 >>> discard = test_publisher.getPubSource(2357 >>> discard = test_publisher.getPubSource(
2358 ... sourcename="pack", version="1.0", archive=cprov.archive)2358 ... sourcename="package2", version="1.0", archive=cprov.archive,
2359 ... status=PackagePublishingStatus.PUBLISHED)
2360 >>> discard = test_publisher.getPubSource(
2361 ... sourcename="pack", version="1.0", archive=cprov.archive,
2362 ... status=PackagePublishingStatus.PUBLISHED)
23592363
2360Now we have package1 1.0 and 1.1, and package2 1.0 in cprov's PPA. We2364Now we have package1 1.0 and 1.1, and package2 1.0 in cprov's PPA. We
2361can ask syncSources to synchronise these packages into mark's PPA in the2365can ask syncSources to synchronise these packages into mark's PPA in the
23622366
=== modified file 'lib/lp/soyuz/interfaces/archive.py'
--- lib/lp/soyuz/interfaces/archive.py 2010-01-12 03:45:02 +0000
+++ lib/lp/soyuz/interfaces/archive.py 2010-02-02 12:06:20 +0000
@@ -896,8 +896,8 @@
896 to_series=None, include_binaries=False):896 to_series=None, include_binaries=False):
897 """Synchronise (copy) named sources into this archive from another.897 """Synchronise (copy) named sources into this archive from another.
898898
899 It will copy the most recent versions of the named sources to899 It will copy the most recent PUBLISHED versions of the named
900 the destination archive if necessary.900 sources to the destination archive if necessary.
901901
902 This operation will only succeeds when all requested packages902 This operation will only succeeds when all requested packages
903 are synchronised between the archives. If any of the requested903 are synchronised between the archives. If any of the requested
904904
=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py 2010-01-12 08:31:42 +0000
+++ lib/lp/soyuz/model/archive.py 2010-02-02 12:06:20 +0000
@@ -1096,17 +1096,8 @@
1096 to_series=None, include_binaries=False):1096 to_series=None, include_binaries=False):
1097 """See `IArchive`."""1097 """See `IArchive`."""
1098 # Find and validate the source package names in source_names.1098 # Find and validate the source package names in source_names.
1099 sources = []1099 sources = self._collectLatestPublishedSources(
1100 for name in source_names:1100 from_archive, source_names)
1101 # Check to see if the source package exists, and raise a useful
1102 # error if it doesn't.
1103 getUtility(ISourcePackageNameSet)[name]
1104 # Grabbing the item at index 0 ensures it's the most recent
1105 # publication.
1106 sources.append(
1107 from_archive.getPublishedSources(
1108 name=name, exact_match=True)[0])
1109
1110 self._copySources(sources, to_pocket, to_series, include_binaries)1101 self._copySources(sources, to_pocket, to_series, include_binaries)
11111102
1112 def syncSource(self, source_name, version, from_archive, to_pocket,1103 def syncSource(self, source_name, version, from_archive, to_pocket,
@@ -1121,6 +1112,28 @@
11211112
1122 self._copySources([source], to_pocket, to_series, include_binaries)1113 self._copySources([source], to_pocket, to_series, include_binaries)
11231114
1115 def _collectLatestPublishedSources(self, from_archive, source_names):
1116 """Private helper to collect the latest published sources for an
1117 archive.
1118
1119 :raises NoSuchSourcePackageName: If any of the source_names do not
1120 exist.
1121 """
1122 sources = []
1123 for name in source_names:
1124 # Check to see if the source package exists. This will raise
1125 # a NoSuchSourcePackageName exception if the source package
1126 # doesn't exist.
1127 getUtility(ISourcePackageNameSet)[name]
1128 # Grabbing the item at index 0 ensures it's the most recent
1129 # publication.
1130 published_sources = from_archive.getPublishedSources(
1131 name=name, exact_match=True,
1132 status=PackagePublishingStatus.PUBLISHED)
1133 if published_sources.count() > 0:
1134 sources.append(published_sources[0])
1135 return sources
1136
1124 def _copySources(self, sources, to_pocket, to_series=None,1137 def _copySources(self, sources, to_pocket, to_series=None,
1125 include_binaries=False):1138 include_binaries=False):
1126 """Private helper function to copy sources to this archive.1139 """Private helper function to copy sources to this archive.
11271140
=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt 2009-10-07 08:28:03 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt 2010-02-02 12:06:20 +0000
@@ -576,9 +576,12 @@
576 ... sourcename="package1", version="1.0",576 ... sourcename="package1", version="1.0",
577 ... archive=ubuntu_db.main_archive)577 ... archive=ubuntu_db.main_archive)
578578
579 >>> from lp.soyuz.interfaces.publishing import (
580 ... PackagePublishingStatus)
579 >>> ignore = test_publisher.getPubSource(581 >>> ignore = test_publisher.getPubSource(
580 ... sourcename="package1", version="1.1",582 ... sourcename="package1", version="1.1",
581 ... archive=ubuntu_db.main_archive)583 ... archive=ubuntu_db.main_archive,
584 ... status=PackagePublishingStatus.PUBLISHED)
582585
583 >>> ignore = test_publisher.getPubSource(586 >>> ignore = test_publisher.getPubSource(
584 ... sourcename="package2", version="1.0",587 ... sourcename="package2", version="1.0",
@@ -742,6 +745,8 @@
742745
743 >>> private_publication = test_publisher.createSource(746 >>> private_publication = test_publisher.createSource(
744 ... cprov.archive, 'foocomm', '1.0-1')747 ... cprov.archive, 'foocomm', '1.0-1')
748 >>> private_publication.secure_record.status = (
749 ... PackagePublishingStatus.PUBLISHED)
745750
746 >>> logout()751 >>> logout()
747752
@@ -908,8 +913,10 @@
908version.913version.
909914
910 >>> login('foo.bar@canonical.com')915 >>> login('foo.bar@canonical.com')
911 >>> unused = test_publisher.createSource(916 >>> subsequent_version = test_publisher.createSource(
912 ... cprov.archive, 'foocomm', '1.0-2')917 ... cprov.archive, 'foocomm', '1.0-2')
918 >>> subsequent_version.secure_record.status = (
919 ... PackagePublishingStatus.PUBLISHED)
913 >>> logout()920 >>> logout()
914921
915 >>> print cprov_webservice.named_post(922 >>> print cprov_webservice.named_post(
916923
=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py 2010-01-12 04:21:42 +0000
+++ lib/lp/soyuz/tests/test_archive.py 2010-02-02 12:06:20 +0000
@@ -513,6 +513,50 @@
513 self.archive.disable()513 self.archive.disable()
514 self.assertRaises(AssertionError, self.archive.disable)514 self.assertRaises(AssertionError, self.archive.disable)
515515
516class TestCollectLatestPublishedSources(TestCaseWithFactory):
517 """Ensure that the private helper method works as expected."""
518
519 layer = LaunchpadZopelessLayer
520
521 def setUp(self):
522 """Setup an archive with relevant publications."""
523 super(TestCollectLatestPublishedSources, self).setUp()
524 self.publisher = SoyuzTestPublisher()
525 self.publisher.prepareBreezyAutotest()
526
527 # Create an archive with some published sources. We'll store
528 # a reference to the naked archive so that we can call
529 # the private method which is not defined on the interface.
530 self.archive = self.factory.makeArchive()
531 self.naked_archive = removeSecurityProxy(self.archive)
532
533 self.pub_1 = self.publisher.getPubSource(
534 version='0.5.11~ppa1', archive=self.archive, sourcename="foo",
535 status=PackagePublishingStatus.PUBLISHED)
536
537 self.pub_2 = self.publisher.getPubSource(
538 version='0.5.11~ppa2', archive=self.archive, sourcename="foo",
539 status=PackagePublishingStatus.PUBLISHED)
540
541 self.pub_3 = self.publisher.getPubSource(
542 version='0.9', archive=self.archive, sourcename="bar",
543 status=PackagePublishingStatus.PUBLISHED)
544
545 def test_collectLatestPublishedSources_returns_latest(self):
546 pubs = self.naked_archive._collectLatestPublishedSources(
547 self.archive, ["foo"])
548 self.assertEqual(1, len(pubs))
549 self.assertEqual('0.5.11~ppa2', pubs[0].source_package_version)
550
551 def test_collectLatestPublishedSources_returns_published_only(self):
552 # Set the status of the latest pub to DELETED and ensure that it
553 # is not returned.
554 self.pub_2.secure_record.status = PackagePublishingStatus.DELETED
555
556 pubs = self.naked_archive._collectLatestPublishedSources(
557 self.archive, ["foo"])
558 self.assertEqual(1, len(pubs))
559 self.assertEqual('0.5.11~ppa1', pubs[0].source_package_version)
516560
517def test_suite():561def test_suite():
518 return unittest.TestLoader().loadTestsFromName(__name__)562 return unittest.TestLoader().loadTestsFromName(__name__)