← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/bug-1501134 into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/bug-1501134 into lp:launchpad.

Commit message:
Test and fix +merges and +activereviews for all targets.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #1501134 in Launchpad itself: "+activereviews OOPSing on most targets when empty"
  https://bugs.launchpad.net/launchpad/+bug/1501134

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/bug-1501134/+merge/272880

Test and fix +merges and +activereviews for all targets.

The SourcePackage views were totally broken, as IGitCollection doesn't (and can't) love SourcePackage. Most +activereviews views were additionally broken on all contexts except GitRef when they listed no MPs, as few contexts have display_name. And +merges was broken on GitRef for the opposite reason.

Several of the new generic tests duplicate some specific old ones, but they can be cleaned up later.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/bug-1501134 into lp:launchpad.
=== modified file 'lib/lp/code/browser/branchmergeproposallisting.py'
--- lib/lp/code/browser/branchmergeproposallisting.py	2015-09-25 14:19:36 +0000
+++ lib/lp/code/browser/branchmergeproposallisting.py	2015-09-30 09:05:40 +0000
@@ -408,7 +408,7 @@
     @property
     def no_proposal_message(self):
         """Shown when there is no table to show."""
-        return "%s has no active code reviews." % self.context.display_name
+        return "%s has no active code reviews." % self.context.displayname
 
 
 class BranchActiveReviewsView(ActiveReviewsView):

=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposallisting.py'
--- lib/lp/code/browser/tests/test_branchmergeproposallisting.py	2015-09-25 14:19:36 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposallisting.py	2015-09-30 09:05:40 +0000
@@ -10,12 +10,16 @@
 import pytz
 from testtools.content import Content
 from testtools.content_type import UTF8_TEXT
-from testtools.matchers import Equals
+from testtools.matchers import (
+    Equals,
+    LessThan,
+    )
 import transaction
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.enums import InformationType
+from lp.app.interfaces.services import IService
 from lp.code.browser.branchmergeproposallisting import (
     ActiveReviewsView,
     BranchMergeProposalListingItem,
@@ -26,9 +30,12 @@
     )
 from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.gitrepository import IGitRepositorySet
+from lp.registry.enums import SharingPermission
 from lp.registry.model.personproduct import PersonProduct
 from lp.services.database.sqlbase import flush_database_caches
+from lp.services.webapp.publisher import canonical_url
 from lp.testing import (
+    admin_logged_in,
     ANONYMOUS,
     BrowserTestCase,
     login,
@@ -236,52 +243,10 @@
     """Test the vote summary for Git."""
 
 
-class TestMerges(BrowserTestCase):
+class TestMergesOnce(BrowserTestCase):
 
     layer = DatabaseFunctionalLayer
 
-    def test_person_product(self):
-        """The merges view should be enabled for PersonProduct."""
-        personproduct = PersonProduct(
-            self.factory.makePerson(), self.factory.makeProduct())
-        self.getViewBrowser(personproduct, '+merges', rootsite='code')
-
-    def test_DistributionSourcePackage(self):
-        """The merges view should be enabled for DistributionSourcePackage."""
-        package = self.factory.makeDistributionSourcePackage()
-        self.getViewBrowser(package, '+merges', rootsite='code')
-
-    def test_query_count_bzr(self):
-        product = self.factory.makeProduct()
-        target = self.factory.makeBranch(
-            product=product, information_type=InformationType.USERDATA)
-        for i in range(7):
-            source = self.factory.makeBranch(
-                product=product, information_type=InformationType.USERDATA)
-            self.factory.makeBranchMergeProposal(
-                source_branch=removeSecurityProxy(source),
-                target_branch=target)
-        flush_database_caches()
-        with StormStatementRecorder() as recorder:
-            self.getViewBrowser(
-                product, '+merges', rootsite='code', user=product.owner)
-        self.assertThat(recorder, HasQueryCount(Equals(41)))
-
-    def test_query_count_git(self):
-        product = self.factory.makeProduct()
-        [target] = self.factory.makeGitRefs(
-            target=product, information_type=InformationType.USERDATA)
-        for i in range(7):
-            [source] = self.factory.makeGitRefs(
-                target=product, information_type=InformationType.USERDATA)
-            self.factory.makeBranchMergeProposalForGit(
-                source_ref=source, target_ref=target)
-        flush_database_caches()
-        with StormStatementRecorder() as recorder:
-            self.getViewBrowser(
-                product, '+merges', rootsite='code', user=product.owner)
-        self.assertThat(recorder, HasQueryCount(Equals(38)))
-
     def test_productseries_bzr(self):
         target = self.factory.makeBranch()
         with person_logged_in(target.product.owner):
@@ -302,6 +267,313 @@
         self.assertIn(identity, view.contents)
 
 
+class BranchMergeProposalListingTestMixin:
+
+    layer = DatabaseFunctionalLayer
+
+    supports_privacy = True
+    supports_git = True
+    supports_bzr = True
+
+    bzr_branch = None
+    git_ref = None
+
+    def makeBzrMergeProposal(self):
+        information_type = (
+            InformationType.USERDATA if self.supports_privacy else None)
+        target = self.bzr_branch
+        if target is None:
+            target = self.factory.makeBranch(
+                target=self.bzr_target, information_type=information_type)
+        source = self.factory.makeBranch(
+            target=self.bzr_target, owner=self.owner,
+            information_type=information_type)
+        return self.factory.makeBranchMergeProposal(
+            source_branch=source, target_branch=target,
+            set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
+
+    def makeGitMergeProposal(self):
+        information_type = (
+            InformationType.USERDATA if self.supports_privacy else None)
+        target = self.git_ref
+        if target is None:
+            [target] = self.factory.makeGitRefs(
+                target=self.git_target, information_type=information_type)
+        [source] = self.factory.makeGitRefs(
+            target=self.git_target, owner=self.owner,
+            information_type=information_type)
+        return self.factory.makeBranchMergeProposalForGit(
+            source_ref=source, target_ref=target,
+            set_state=BranchMergeProposalStatus.NEEDS_REVIEW)
+
+    def test_bzr(self):
+        """The merges view should be enabled for the target."""
+        if not self.supports_bzr:
+            self.skipTest("Context doesn't support Bazaar branches.")
+        with admin_logged_in():
+            bmp = self.makeBzrMergeProposal()
+            url = canonical_url(bmp, force_local_path=True)
+        browser = self.getViewBrowser(
+            self.context, self.view_name, rootsite='code', user=self.user)
+        self.assertIn(url, browser.contents)
+
+    def test_git(self):
+        """The merges view should be enabled for the target."""
+        if not self.supports_git:
+            self.skipTest("Context doesn't support Git repositories.")
+        with admin_logged_in():
+            bmp = self.makeGitMergeProposal()
+            url = canonical_url(bmp, force_local_path=True)
+        browser = self.getViewBrowser(
+            self.context, self.view_name, rootsite='code', user=self.user)
+        self.assertIn(url, browser.contents)
+
+    def test_query_count_bzr(self):
+        if not self.supports_bzr:
+            self.skipTest("Context doesn't support Bazaar branches.")
+        with admin_logged_in():
+            for i in range(7):
+                self.makeBzrMergeProposal()
+        flush_database_caches()
+        with StormStatementRecorder() as recorder:
+            self.getViewBrowser(
+                self.context, self.view_name, rootsite='code', user=self.user)
+        self.assertThat(recorder, HasQueryCount(LessThan(51)))
+
+    def test_query_count_git(self):
+        if not self.supports_git:
+            self.skipTest("Context doesn't support Git repositories.")
+        with admin_logged_in():
+            for i in range(7):
+                self.makeGitMergeProposal()
+        flush_database_caches()
+        with StormStatementRecorder() as recorder:
+            self.getViewBrowser(
+                self.context, self.view_name, rootsite='code', user=self.user)
+        self.assertThat(recorder, HasQueryCount(LessThan(47)))
+
+
+class MergesTestMixin(BranchMergeProposalListingTestMixin):
+
+    view_name = '+merges'
+
+    def test_none(self):
+        """The merges view should be enabled for the target."""
+        browser = self.getViewBrowser(
+            self.context, self.view_name, rootsite='code', user=self.user)
+        self.assertIn("has no merge proposals", browser.contents)
+
+
+class ActiveReviewsTestMixin(BranchMergeProposalListingTestMixin):
+
+    view_name = '+activereviews'
+
+    def test_none(self):
+        """The active reviews view should be enabled for the target."""
+        browser = self.getViewBrowser(
+            self.context, self.view_name, rootsite='code', user=self.user)
+        self.assertIn("has no active code reviews", browser.contents)
+
+
+class ProductContextMixin:
+
+    def setUp(self):
+        super(ProductContextMixin, self).setUp()
+        self.context = self.factory.makeProduct()
+        self.git_target = self.bzr_target = self.context = (
+            self.factory.makeProduct())
+        self.user = self.git_target.owner
+        self.owner = None
+
+
+class ProjectGroupContextMixin:
+
+    def setUp(self):
+        super(ProjectGroupContextMixin, self).setUp()
+        self.context = self.factory.makeProject()
+        self.git_target = self.bzr_target = self.factory.makeProduct(
+            projectgroup=self.context)
+        self.user = self.git_target.owner
+        self.owner = None
+
+
+class DistributionSourcePackageContextMixin:
+
+    # Distribution branches don't have access_policy set.
+    supports_privacy = False
+
+    def setUp(self):
+        super(DistributionSourcePackageContextMixin, self).setUp()
+        self.git_target = self.context = (
+            self.factory.makeDistributionSourcePackage())
+        with admin_logged_in():
+            getUtility(IService, "sharing").sharePillarInformation(
+                self.context.distribution, self.context.distribution.owner,
+                self.context.distribution.owner,
+                {InformationType.USERDATA: SharingPermission.ALL})
+        distroseries = self.factory.makeDistroSeries(
+            distribution=self.context.distribution)
+        self.bzr_target = distroseries.getSourcePackage(
+            self.context.sourcepackagename)
+        self.user = self.context.distribution.owner
+        self.owner = None
+
+
+class SourcePackageContextMixin:
+
+    # Distribution branches don't have access_policy set.
+    supports_privacy = False
+    supports_git = False
+
+    def setUp(self):
+        super(SourcePackageContextMixin, self).setUp()
+        self.bzr_target = self.context = self.factory.makeSourcePackage()
+        self.user = self.context.distribution.owner
+        self.owner = None
+
+
+class PersonContextMixin:
+
+    def setUp(self):
+        super(PersonContextMixin, self).setUp()
+        self.context = self.factory.makePerson()
+        self.bzr_target = self.git_target = self.factory.makeProduct()
+        self.user = self.bzr_target.owner
+        self.owner = self.context
+
+
+class PersonProductContextMixin:
+
+    def setUp(self):
+        super(PersonProductContextMixin, self).setUp()
+        self.context = PersonProduct(
+            self.factory.makePerson(), self.factory.makeProduct())
+        self.bzr_target = self.git_target = self.context.product
+        self.user = self.context.product.owner
+        self.owner = self.context.person
+
+
+class BranchContextMixin:
+
+    supports_git = False
+
+    def setUp(self):
+        super(BranchContextMixin, self).setUp()
+        self.bzr_target = self.factory.makeProduct()
+        self.context = self.bzr_branch = self.factory.makeBranch(
+            target=self.bzr_target)
+        self.user = self.bzr_target.owner
+        self.owner = None
+
+
+class GitRefContextMixin:
+
+    supports_bzr = False
+
+    def setUp(self):
+        super(GitRefContextMixin, self).setUp()
+        self.git_target = self.factory.makeProduct()
+        self.context = self.git_ref = self.factory.makeGitRefs(
+            target=self.git_target)[0]
+        self.user = self.git_target.owner
+        self.owner = None
+
+
+class TestProductMerges(
+        ProductContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestProjectGroupMerges(
+        ProjectGroupContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestDistributionSourcePackageMerges(
+        DistributionSourcePackageContextMixin, MergesTestMixin,
+        BrowserTestCase):
+
+    pass
+
+
+class TestSourcePackageMerges(
+        SourcePackageContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestPersonMerges(PersonContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestPersonProductMerges(
+        PersonProductContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestBranchMerges(BranchContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestGitRefMerges(GitRefContextMixin, MergesTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestProductActiveReviews(
+        ProductContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestProjectGroupActiveReviews(
+        ProjectGroupContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestDistributionSourcePackageActiveReviews(
+        DistributionSourcePackageContextMixin, ActiveReviewsTestMixin,
+        BrowserTestCase):
+
+    pass
+
+
+class TestSourcePackageActiveReviews(
+        SourcePackageContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestPersonActiveReviews(
+        PersonContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestPersonProductActiveReviews(
+        PersonProductContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestBranchActiveReviews(
+        BranchContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
+class TestGitRefActiveReviews(
+        GitRefContextMixin, ActiveReviewsTestMixin, BrowserTestCase):
+
+    pass
+
+
 class ActiveReviewGroupsTestMixin:
     """Tests for groupings used for active reviews."""
 
@@ -536,8 +808,8 @@
     """Test reviews of references in Git repositories."""
 
 
-class PersonActiveReviewsPerformanceMixin:
-    """Test the performance of the person's active reviews page."""
+class ActiveReviewsPerformanceMixin:
+    """Test the performance of the active reviews page."""
 
     layer = LaunchpadFunctionalLayer
 
@@ -627,11 +899,11 @@
         self.assertThat(recorder2, HasQueryCount(Equals(recorder1.count)))
 
 
-class PersonActiveReviewsPerformanceBzr(
-    PersonActiveReviewsPerformanceMixin, BzrMixin, TestCaseWithFactory):
-    """Test the performance of the person's active reviews page for Bazaar."""
-
-
-class PersonActiveReviewsPerformanceGit(
-    PersonActiveReviewsPerformanceMixin, GitMixin, TestCaseWithFactory):
-    """Test the performance of the person's active reviews page for Git."""
+class ActiveReviewsPerformanceBzr(
+    ActiveReviewsPerformanceMixin, BzrMixin, TestCaseWithFactory):
+    """Test the performance of the active reviews page for Bazaar."""
+
+
+class ActiveReviewsPerformanceGit(
+    ActiveReviewsPerformanceMixin, GitMixin, TestCaseWithFactory):
+    """Test the performance of the active reviews page for Git."""

=== modified file 'lib/lp/code/interfaces/gitref.py'
--- lib/lp/code/interfaces/gitref.py	2015-09-17 08:52:02 +0000
+++ lib/lp/code/interfaces/gitref.py	2015-09-30 09:05:40 +0000
@@ -107,6 +107,9 @@
         title=_("Display name"), required=True, readonly=True,
         description=_("Display name of the reference."))
 
+    displayname = Attribute(
+        "Copy of display_name for IHasMergeProposals views.")
+
     commit_message_first_line = TextLine(
         title=_("The first line of the commit message."),
         required=True, readonly=True)

=== modified file 'lib/lp/code/model/gitref.py'
--- lib/lp/code/model/gitref.py	2015-09-21 09:57:57 +0000
+++ lib/lp/code/model/gitref.py	2015-09-30 09:05:40 +0000
@@ -65,6 +65,9 @@
         """See `IGitRef`."""
         return self.identity
 
+    # For IHasMergeProposals views.
+    displayname = display_name
+
     @property
     def name(self):
         """See `IGitRef`."""

=== modified file 'lib/lp/code/model/hasbranches.py'
--- lib/lp/code/model/hasbranches.py	2015-05-12 17:30:40 +0000
+++ lib/lp/code/model/hasbranches.py	2015-09-30 09:05:40 +0000
@@ -27,6 +27,7 @@
     IAllGitRepositories,
     IGitCollection,
     )
+from lp.registry.interfaces.sourcepackage import ISourcePackage
 from lp.services.database.decoratedresultset import DecoratedResultSet
 
 
@@ -66,8 +67,13 @@
             collection = collection.visibleByUser(visible_by_user)
             return collection.getMergeProposals(status, eager_load=False)
 
-        proposals = _getProposals(IBranchCollection).union(
-            _getProposals(IGitCollection))
+        # SourcePackage Bazaar branches are an aberration which was not
+        # replicated for Git, so SourcePackage does not support Git.
+        if ISourcePackage.providedBy(self):
+            proposals = _getProposals(IBranchCollection)
+        else:
+            proposals = _getProposals(IBranchCollection).union(
+                _getProposals(IGitCollection))
         if not eager_load:
             return proposals
         else:


Follow ups