← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/dsp-official-branches-0 into lp:launchpad

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/dsp-official-branches-0 into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #851276 in Launchpad itself: "Update DSP when an official package branch is linked"
  https://bugs.launchpad.net/launchpad/+bug/851276

For more details, see:
https://code.launchpad.net/~sinzui/launchpad/dsp-official-branches-0/+merge/76128

Ensure official package branches are represented in the DSP table.

    Launchpad bug: https://bugs.launchpad.net/bugs/851276
    Pre-implementation: jcsackett

Ensure that official package branches have an entry in the DSP table.
Any branch linked to a package name or uploaded package to a distribution
represents an official package that user can report bugs or ask questions
about.

--------------------------------------------------------------------

RULES

    * Update DSP.ensure to accept an alternate argument for SPPH that
      provides the distro and spn to create the DSP table row.
      * Both DistributionPackageLinkedBranch and PackageLinkedBranch
        require a SuiteSourcePackage to accomplish the setBranch(). This
        object provides both a SPN and a distro.
      * There are no unittests or doctests that specifically verify that
        DSP.ensure works.
      * ADDENDUM: add a delete method that only deletes if it is safe to
        delete a persistent DSP.
    * Update SourcePackage.setBranch() to call DSP.ensure()
      * Update test_sourcepackage testcases to verify that linking to
        a branch creates an official dsp.
      * Verify that the the persistent DSP is removed if there is not SPPH.


QA

    * For a distro you can set the official branches find a package that
      has never been uploaded/published.
    * Verify the package is not listed in the package picker if the dsp flag
      is enabled, or check that the DSP table *does not* have an entry.
    * Link the distro or current series package to a branch.
    * Verify that the package is listed in the package picker if the dsp flag
      is enabled, or check that the DSP table *does* have an entry.
    * Unlink the the package.
    * Verify the package is not listed in the package picker if the dsp flag
      is enabled, or check that the DSP table *does not* have an entry.


LINT

    lib/lp/registry/configure.zcml
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/registry/model/distributionsourcepackage.py
    lib/lp/registry/model/sourcepackage.py
    lib/lp/registry/tests/test_distributionsourcepackage.py
    lib/lp/registry/tests/test_sourcepackage.py


TEST

    ./bin/test -vvc lp.registry.tests.test_distributionsourcepackage
    ./bin/test -vvc lp.registry.tests.test_sourcepackage


IMPLEMENTATION

Updated ensure() to accept a SourcePackage or SPPH. Added a delete method
that only deletes a persistent DSP if there is not SPPH.
    lib/lp/registry/configure.zcml
    lib/lp/registry/interfaces/distributionsourcepackage.py
    lib/lp/registry/model/distributionsourcepackage.py
    lib/lp/registry/tests/test_distributionsourcepackage.py

Updated setBranch() to call DSP.ensure() after setting the branch, and
call DSP.delete() when the branch was set to None.
    lib/lp/registry/model/sourcepackage.py
    lib/lp/registry/tests/test_sourcepackage.py
-- 
https://code.launchpad.net/~sinzui/launchpad/dsp-official-branches-0/+merge/76128
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/dsp-official-branches-0 into lp:launchpad.
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2011-09-15 18:45:21 +0000
+++ lib/lp/registry/configure.zcml	2011-09-19 23:00:30 +0000
@@ -481,6 +481,7 @@
                 critical_bugtasks
                 current_publishing_records
                 currentrelease
+                delete
                 development_version
                 displayname
                 distribution

=== modified file 'lib/lp/registry/interfaces/distributionsourcepackage.py'
--- lib/lp/registry/interfaces/distributionsourcepackage.py	2011-05-12 21:51:01 +0000
+++ lib/lp/registry/interfaces/distributionsourcepackage.py	2011-09-19 23:00:30 +0000
@@ -210,3 +210,9 @@
         Distro sourcepackages compare not equal if either of their
         distribution or sourcepackagename compare not equal.
         """
+
+    def delete():
+        """Delete the persistent DSP if it exists.
+
+        :return: True if a persistent object was removed, otherwise False.
+        """

=== modified file 'lib/lp/registry/model/distributionsourcepackage.py'
--- lib/lp/registry/model/distributionsourcepackage.py	2011-09-16 19:02:49 +0000
+++ lib/lp/registry/model/distributionsourcepackage.py	2011-09-19 23:00:30 +0000
@@ -207,6 +207,16 @@
         # in the database.
         return self._get(self.distribution, self.sourcepackagename)
 
+    def delete(self):
+        """See `DistributionSourcePackage`."""
+        dsp_in_db = self._self_in_database
+        no_spph = self.publishing_history.count() == 0
+        if dsp_in_db is not None and no_spph:
+            store = IStore(dsp_in_db)
+            store.remove(dsp_in_db)
+            return True
+        return False
+
     def recalculateBugHeatCache(self):
         """See `IHasBugHeat`."""
         row = IStore(Bug).find(
@@ -527,16 +537,30 @@
         return dsp
 
     @classmethod
-    def ensure(cls, spph):
+    def ensure(cls, spph=None, sourcepackage=None):
         """Create DistributionSourcePackage record, if necessary.
 
-        Only create a record for primary archives (i.e. not for PPAs).
+        Only create a record for primary archives (i.e. not for PPAs) or
+        for official package branches. Requires either a SourcePackage
+        or a SourcePackagePublishingHistory.
+
+        :param spph: A SourcePackagePublishingHistory to create a DSP
+            to represent an official uploaded/published package.
+        :param sourcepackage: A SourcePackage to create a DSP to represent an
+            official package branch.
         """
-        if spph.archive.purpose != ArchivePurpose.PRIMARY:
-            return
-
-        distribution = spph.distroseries.distribution
-        sourcepackagename = spph.sourcepackagerelease.sourcepackagename
+        if spph is None and sourcepackage is None:
+            raise ValueError(
+                'ensure() must be called with either a SPPH '
+                'or a SourcePackage.')
+        if spph is not None:
+            if spph.archive.purpose != ArchivePurpose.PRIMARY:
+                return
+            distribution = spph.distroseries.distribution
+            sourcepackagename = spph.sourcepackagerelease.sourcepackagename
+        else:
+            distribution = sourcepackage.distribution
+            sourcepackagename = sourcepackage.sourcepackagename
         dsp = cls._get(distribution, sourcepackagename)
         if dsp is None:
             upstream_link_allowed = is_upstream_link_allowed(spph)
@@ -572,8 +596,7 @@
     po_message_count = Int()
     is_upstream_link_allowed = Bool()
     enable_bugfiling_duplicate_search = Bool()
-    
-    # XXX kiko 2006-08-16: Bad method name, no need to be a property.
+
     @property
     def currentrelease(self):
         """See `IDistributionSourcePackage`."""

=== modified file 'lib/lp/registry/model/sourcepackage.py'
--- lib/lp/registry/model/sourcepackage.py	2011-09-13 05:23:16 +0000
+++ lib/lp/registry/model/sourcepackage.py	2011-09-19 23:00:30 +0000
@@ -754,6 +754,14 @@
             SeriesSourcePackageBranchSet.new(
                 self.distroseries, pocket, self.sourcepackagename, branch,
                 registrant)
+            # Avoid circular imports.
+            from lp.registry.model.distributionsourcepackage import (
+                DistributionSourcePackage,
+                )
+            DistributionSourcePackage.ensure(sourcepackage=self)
+        else:
+            # Delete the official DSP if there is no publishing history.
+            self.distribution_sourcepackage.delete()
 
     @property
     def linked_branches(self):

=== modified file 'lib/lp/registry/tests/test_distributionsourcepackage.py'
--- lib/lp/registry/tests/test_distributionsourcepackage.py	2011-08-12 11:37:08 +0000
+++ lib/lp/registry/tests/test_distributionsourcepackage.py	2011-09-19 23:00:30 +0000
@@ -9,11 +9,16 @@
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
+from canonical.launchpad.interfaces.lpstorm import IStore
 from canonical.testing.layers import (
     DatabaseFunctionalLayer,
     LaunchpadZopelessLayer,
     )
 from lp.registry.interfaces.distribution import IDistributionSet
+from lp.registry.model.distributionsourcepackage import (
+    DistributionSourcePackage,
+    DistributionSourcePackageInDatabase,
+    )
 from lp.registry.model.karma import KarmaTotalCache
 from lp.soyuz.enums import PackagePublishingStatus
 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
@@ -37,6 +42,79 @@
         dsp = naked_distribution.getSourcePackage(name='pmount')
         self.assertEqual(None, dsp.summary)
 
+    def test_ensure_spph_creates_a_dsp_in_db(self):
+        # The DSP.ensure() class methods creates a persistent instance
+        # if one does not exist.
+        spph = self.factory.makeSourcePackagePublishingHistory()
+        spph_dsp = spph.sourcepackagerelease.distrosourcepackage
+        DistributionSourcePackage.ensure(spph)
+        new_dsp = DistributionSourcePackage._get(
+            spph_dsp.distribution, spph_dsp.sourcepackagename)
+        self.assertIsNot(None, new_dsp)
+        self.assertIsNot(spph_dsp, new_dsp)
+        self.assertEqual(spph_dsp.distribution, new_dsp.distribution)
+        self.assertEqual(
+            spph_dsp.sourcepackagename, new_dsp.sourcepackagename)
+
+    def test_ensure_spph_dsp_in_db_exists(self):
+        # The DSP.ensure() class methods does not create duplicate
+        # persistent instances; it skips the query to create the DSP.
+        store = IStore(DistributionSourcePackageInDatabase)
+        start_count = store.find(DistributionSourcePackageInDatabase).count()
+        spph = self.factory.makeSourcePackagePublishingHistory()
+        DistributionSourcePackage.ensure(spph)
+        new_count = store.find(DistributionSourcePackageInDatabase).count()
+        self.assertEqual(start_count + 1, new_count)
+        final_count = store.find(DistributionSourcePackageInDatabase).count()
+        self.assertEqual(new_count, final_count)
+
+    def test_ensure_spph_does_not_create_dsp_in_db_non_primary_archive(self):
+        # The DSP.ensure() class methods creates a persistent instance
+        # if one does not exist.
+        archive = self.factory.makeArchive()
+        spph = self.factory.makeSourcePackagePublishingHistory(
+            archive=archive)
+        spph_dsp = spph.sourcepackagerelease.distrosourcepackage
+        DistributionSourcePackage.ensure(spph)
+        new_dsp = DistributionSourcePackage._get(
+            spph_dsp.distribution, spph_dsp.sourcepackagename)
+        self.assertIs(None, new_dsp)
+
+    def test_ensure_suitesourcepackage_creates_a_dsp_in_db(self):
+        # The DSP.ensure() class methods creates a persistent instance
+        # if one does not exist.
+        sourcepackage = self.factory.makeSourcePackage()
+        DistributionSourcePackage.ensure(sourcepackage=sourcepackage)
+        new_dsp = DistributionSourcePackage._get(
+            sourcepackage.distribution, sourcepackage.sourcepackagename)
+        self.assertIsNot(None, new_dsp)
+        self.assertEqual(sourcepackage.distribution, new_dsp.distribution)
+        self.assertEqual(
+            sourcepackage.sourcepackagename, new_dsp.sourcepackagename)
+
+    def test_delete_without_dsp_in_db(self):
+        # Calling delete() on a DSP without persistence returns False.
+        dsp = self.factory.makeDistributionSourcePackage()
+        self.assertFalse(dsp.delete())
+
+    def test_delete_with_dsp_in_db_with_history(self):
+        # Calling delete() on a persistent DSP with SPPH returns False.
+        # Once a package is uploaded, it cannot be deleted.
+        spph = self.factory.makeSourcePackagePublishingHistory()
+        dsp = spph.sourcepackagerelease.distrosourcepackage
+        DistributionSourcePackage.ensure(spph=spph)
+        transaction.commit()
+        self.assertFalse(dsp.delete())
+
+    def test_delete_with_dsp_in_db_without_history(self):
+        # Calling delete() on a persistent DSP without SPPH returns True.
+        # A package without history was a mistake.
+        sp = self.factory.makeSourcePackage()
+        DistributionSourcePackage.ensure(sourcepackage=sp)
+        transaction.commit()
+        dsp = sp.distribution_sourcepackage
+        self.assertTrue(dsp.delete())
+
 
 class TestDistributionSourcePackageFindRelatedArchives(TestCaseWithFactory):
 

=== modified file 'lib/lp/registry/tests/test_sourcepackage.py'
--- lib/lp/registry/tests/test_sourcepackage.py	2011-08-12 11:37:08 +0000
+++ lib/lp/registry/tests/test_sourcepackage.py	2011-09-19 23:00:30 +0000
@@ -24,6 +24,9 @@
 from lp.registry.interfaces.distribution import NoPartnerArchive
 from lp.registry.interfaces.pocket import PackagePublishingPocket
 from lp.registry.interfaces.series import SeriesStatus
+from lp.registry.model.distributionsourcepackage import (
+    DistributionSourcePackage,
+    )
 from lp.registry.model.packaging import Packaging
 from lp.soyuz.enums import (
     ArchivePurpose,
@@ -81,6 +84,10 @@
         with person_logged_in(sourcepackage.distribution.owner):
             sourcepackage.setBranch(pocket, branch, registrant)
         self.assertEqual(branch, sourcepackage.getBranch(pocket))
+        # A DSP was created for the official branch.
+        new_dsp = DistributionSourcePackage._get(
+            sourcepackage.distribution, sourcepackage.sourcepackagename)
+        self.assertIsNot(None, new_dsp)
 
     def test_change_branch_once_set(self):
         # We can change the official branch for a a pocket of a source package
@@ -108,6 +115,20 @@
             sourcepackage.setBranch(pocket, None, registrant)
         self.assertIs(None, sourcepackage.getBranch(pocket))
 
+    def test_unsetBranch_delete_unpublished_dsp(self):
+        # Setting the official branch for a pocket to 'None' deletes the
+        # official DSP record if there is no SPPH.
+        sourcepackage = self.factory.makeSourcePackage()
+        pocket = PackagePublishingPocket.RELEASE
+        registrant = self.factory.makePerson()
+        branch = self.factory.makePackageBranch(sourcepackage=sourcepackage)
+        with person_logged_in(sourcepackage.distribution.owner):
+            sourcepackage.setBranch(pocket, branch, registrant)
+            sourcepackage.setBranch(pocket, None, registrant)
+        new_dsp = DistributionSourcePackage._get(
+            sourcepackage.distribution, sourcepackage.sourcepackagename)
+        self.assertIs(None, new_dsp)
+
     def test_linked_branches(self):
         # ISourcePackage.linked_branches is a mapping of pockets to branches.
         sourcepackage = self.factory.makeSourcePackage()