← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~abentley/launchpad/unnecessary-upgrade into lp:launchpad

 

Aaron Bentley has proposed merging lp:~abentley/launchpad/unnecessary-upgrade into lp:launchpad with lp:~abentley/launchpad/upgrade-not-branch-error as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #823850 in Launchpad itself: "AssertionError raised upgrading a branch that doesn't need upgrade"
  https://bugs.launchpad.net/launchpad/+bug/823850

For more details, see:
https://code.launchpad.net/~abentley/launchpad/unnecessary-upgrade/+merge/72480

= Summary =
Fix bug #823850: AssertionError raised upgrading a branch that doesn't need upgrade

== Proposed fix ==
Produce a notification explaining why the branch could not be upgraded.

== Pre-implementation notes ==
None

== Implementation details ==
Extracted the Branch.needs_upgrade logic to Branch.checkUpgrade, so that we can raise exceptions based on the precise checks.

Fixed lint.

== Tests ==
bin/test -t checkUpgrade -t test_upgrade_branch_action_cannot_upgrade

== Demo and Q/A ==
Upgrade a branch, and then immediately request another upgrade.  You should get a nice error notification.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/errors.py
  lib/lp/code/browser/branch.py
  lib/lp/code/browser/tests/test_branch.py
  lib/lp/code/interfaces/branch.py
  lib/lp/code/model/branch.py
  lib/lp/code/model/branchjob.py
  lib/lp/code/model/tests/test_branch.py
-- 
https://code.launchpad.net/~abentley/launchpad/unnecessary-upgrade/+merge/72480
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/unnecessary-upgrade into lp:launchpad.
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2011-08-22 18:45:27 +0000
+++ lib/lp/code/browser/branch.py	2011-08-22 18:45:28 +0000
@@ -131,6 +131,7 @@
 from lp.code.errors import (
     BranchCreationForbidden,
     BranchExists,
+    CannotUpgradeBranch,
     CodeImportAlreadyRequested,
     CodeImportAlreadyRunning,
     CodeImportNotInReviewedState,
@@ -993,7 +994,10 @@
 
     @action('Upgrade', name='upgrade_branch')
     def upgrade_branch_action(self, action, data):
-        self.context.requestUpgrade(self.user)
+        try:
+            self.context.requestUpgrade(self.user)
+        except CannotUpgradeBranch as e:
+            self.request.response.addErrorNotification(e)
 
 
 class BranchEditView(BranchEditFormView, BranchNameValidationMixin):

=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py	2011-07-21 02:18:54 +0000
+++ lib/lp/code/browser/tests/test_branch.py	2011-08-22 18:45:28 +0000
@@ -7,12 +7,10 @@
 
 from datetime import (
     datetime,
-    timedelta,
     )
 from textwrap import dedent
 
 import pytz
-import simplejson
 from zope.security.proxy import removeSecurityProxy
 
 from canonical.config import config
@@ -41,7 +39,11 @@
     BranchView,
     )
 from lp.code.browser.branchlisting import PersonOwnedBranchesView
-from lp.code.bzr import ControlFormat
+from lp.code.bzr import (
+    BranchFormat,
+    ControlFormat,
+    RepositoryFormat,
+    )
 from lp.code.enums import (
     BranchLifecycleStatus,
     BranchType,
@@ -400,7 +402,7 @@
 
     def _add_revisions(self, branch, nr_revisions=1):
         revisions = []
-        for seq in range(1, nr_revisions+1):
+        for seq in range(1, nr_revisions + 1):
             revision = self.factory.makeRevision(
                 author="Eric the Viking <eric@xxxxxxxxxxxxxxxxxxxxxxxx>",
                 log_body=(
@@ -806,3 +808,23 @@
                 'Some Product.'))
         with person_logged_in(person):
             self.assertEquals(person, branch.owner)
+
+
+class TestBranchUpgradeView(TestCaseWithFactory):
+
+    layer = LaunchpadFunctionalLayer
+
+    def test_upgrade_branch_action_cannot_upgrade(self):
+        # A nice error is displayed if a branch cannot be upgraded.
+        branch = self.factory.makePersonalBranch(
+        branch_format=BranchFormat.BZR_BRANCH_6,
+        repository_format=RepositoryFormat.BZR_CHK_2A)
+        login_person(branch.owner)
+        self.addCleanup(logout)
+        branch.requestUpgrade(branch.owner)
+        view = create_initialized_view(branch, '+upgrade')
+        view.upgrade_branch_action.success({})
+        self.assertEqual(1, len(view.request.notifications))
+        self.assertEqual(
+            'An upgrade is already in progress for branch %s.' %
+            branch.bzr_identity, view.request.notifications[0].message)

=== modified file 'lib/lp/code/errors.py'
--- lib/lp/code/errors.py	2011-06-23 21:09:20 +0000
+++ lib/lp/code/errors.py	2011-08-22 18:45:28 +0000
@@ -5,6 +5,7 @@
 
 __metaclass__ = type
 __all__ = [
+    'AlreadyLatestFormat',
     'BadBranchMergeProposalSearchContext',
     'BadStateTransition',
     'BranchCannotBePrivate',
@@ -20,6 +21,8 @@
     'BuildNotAllowedForDistro',
     'BranchMergeProposalExists',
     'CannotDeleteBranch',
+    'CannotUpgradeBranch',
+    'CannotUpgradeNonHosted',
     'CannotHaveLinkedBranch',
     'CodeImportAlreadyRequested',
     'CodeImportAlreadyRunning',
@@ -36,6 +39,7 @@
     'TooManyBuilds',
     'TooNewRecipeFormat',
     'UnknownBranchTypeError',
+    'UpgradePending',
     'UserHasExistingReview',
     'UserNotBranchReviewer',
     'WrongBranchMergeProposal',
@@ -167,6 +171,36 @@
     _msg_template = "%s cannot have linked branches."
 
 
+class CannotUpgradeBranch(Exception):
+    """"Made for subclassing."""
+
+    def __init__(self, branch):
+        super(CannotUpgradeBranch, self).__init__(
+            self._msg_template % branch.bzr_identity)
+        self.branch = branch
+
+
+class AlreadyLatestFormat(CannotUpgradeBranch):
+    """Raised on attempt to upgrade a branch already in the latest format."""
+
+    _msg_template = (
+        'Branch %s is in the latest format, so it cannot be upgraded.')
+
+
+class CannotUpgradeNonHosted(CannotUpgradeBranch):
+
+    """Raised on attempt to upgrade a non-Hosted branch."""
+
+    _msg_template = 'Cannot upgrade non-hosted branch %s'
+
+
+class UpgradePending(CannotUpgradeBranch):
+
+    """Raised on attempt to upgrade a branch already in the latest format."""
+
+    _msg_template = 'An upgrade is already in progress for branch %s.'
+
+
 class ClaimReviewFailed(Exception):
     """The user cannot claim the pending review."""
 

=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py	2011-08-22 18:45:27 +0000
+++ lib/lp/code/interfaces/branch.py	2011-08-22 18:45:28 +0000
@@ -944,6 +944,12 @@
         :return: A list of tuples like (date, count).
         """
 
+    def checkUpgrade():
+        """Check whether an upgrade should be performed, and raise if not.
+
+        :raises: a `CannotUpgradeBranch`, or a subclass.
+        """
+
     needs_upgrading = Attribute("Whether the branch needs to be upgraded.")
     upgrade_pending = Attribute(
         "Whether a branch has had an upgrade requested.")

=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py	2011-08-22 18:45:27 +0000
+++ lib/lp/code/model/branch.py	2011-08-22 18:45:28 +0000
@@ -87,14 +87,18 @@
     BranchType,
     )
 from lp.code.errors import (
+    AlreadyLatestFormat,
     BranchCannotBePrivate,
     BranchCannotBePublic,
     BranchMergeProposalExists,
     BranchTargetError,
     BranchTypeError,
     CannotDeleteBranch,
+    CannotUpgradeBranch,
+    CannotUpgradeNonHosted,
     InvalidBranchMergeProposal,
     InvalidMergeQueueConfig,
+    UpgradePending,
     )
 from lp.code.event.branchmergeproposal import (
     BranchMergeProposalNeedsReviewEvent,
@@ -1127,16 +1131,25 @@
             DateTrunc(u'day', Revision.revision_date))
         return sorted(results)
 
-    @property
-    def needs_upgrading(self):
-        """See `IBranch`."""
+    def checkUpgrade(self):
         if self.branch_type is not BranchType.HOSTED:
-            return False
+            raise CannotUpgradeNonHosted(self)
         if self.upgrade_pending:
-            return False
-        return not (
+            raise UpgradePending(self)
+        if (
             self.branch_format in CURRENT_BRANCH_FORMATS and
-            self.repository_format in CURRENT_REPOSITORY_FORMATS)
+            self.repository_format in CURRENT_REPOSITORY_FORMATS):
+            raise AlreadyLatestFormat(self)
+
+    @property
+    def needs_upgrading(self):
+        """See `IBranch`."""
+        try:
+            self.checkUpgrade()
+        except CannotUpgradeBranch:
+            return False
+        else:
+            return True
 
     @property
     def upgrade_pending(self):

=== modified file 'lib/lp/code/model/branchjob.py'
--- lib/lp/code/model/branchjob.py	2011-08-22 18:45:27 +0000
+++ lib/lp/code/model/branchjob.py	2011-08-22 18:45:28 +0000
@@ -378,10 +378,7 @@
     @classmethod
     def create(cls, branch, requester):
         """See `IBranchUpgradeJobSource`."""
-        if not branch.needs_upgrading:
-            raise AssertionError('Branch does not need upgrading.')
-        if branch.upgrade_pending:
-            raise AssertionError('Branch already has upgrade pending.')
+        branch.checkUpgrade()
         branch_job = BranchJob(
             branch, BranchJobType.UPGRADE_BRANCH, {}, requester=requester)
         return cls(branch_job)

=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py	2011-08-22 18:45:27 +0000
+++ lib/lp/code/model/tests/test_branch.py	2011-08-22 18:45:28 +0000
@@ -19,6 +19,7 @@
 import simplejson
 from sqlobject import SQLObjectNotFound
 from storm.locals import Store
+from testtools import ExpectedException
 import transaction
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
@@ -56,14 +57,17 @@
     CodeReviewNotificationLevel,
     )
 from lp.code.errors import (
+    AlreadyLatestFormat,
     BranchCannotBePrivate,
     BranchCannotBePublic,
     BranchCreatorNotMemberOfOwnerTeam,
     BranchCreatorNotOwner,
     BranchTargetError,
     CannotDeleteBranch,
+    CannotUpgradeNonHosted,
     InvalidBranchMergeProposal,
     InvalidMergeQueueConfig,
+    UpgradePending,
     )
 from lp.code.interfaces.branch import (
     DEFAULT_BRANCH_STATUS_IN_LISTING,
@@ -586,6 +590,14 @@
         branch = self.factory.makePersonalBranch()
         self.assertFalse(branch.needs_upgrading)
 
+    def test_checkUpgrade_empty_formats(self):
+        branch = self.factory.makePersonalBranch()
+        with ExpectedException(
+            AlreadyLatestFormat,
+            'Branch lp://dev/~person-name.*junk/branch.* is in the latest'
+            ' format, so it cannot be upgraded.'):
+            branch.checkUpgrade()
+
     def test_needsUpgrade_mirrored_branch(self):
         branch = self.factory.makeBranch(
             branch_type=BranchType.MIRRORED,
@@ -593,6 +605,16 @@
             repository_format=RepositoryFormat.BZR_REPOSITORY_4)
         self.assertFalse(branch.needs_upgrading)
 
+    def test_checkUpgrade_mirrored_branch(self):
+        branch = self.factory.makeBranch(
+            branch_type=BranchType.MIRRORED,
+            branch_format=BranchFormat.BZR_BRANCH_6,
+            repository_format=RepositoryFormat.BZR_REPOSITORY_4)
+        with ExpectedException(
+            CannotUpgradeNonHosted,
+            'Cannot upgrade non-hosted branch %s' % branch.bzr_identity):
+            branch.checkUpgrade()
+
     def test_needsUpgrade_remote_branch(self):
         branch = self.factory.makeBranch(
             branch_type=BranchType.REMOTE,
@@ -621,6 +643,20 @@
 
         self.assertFalse(branch.needs_upgrading)
 
+    def test_checkUpgrade_already_requested(self):
+        branch = self.factory.makePersonalBranch(
+            branch_format=BranchFormat.BZR_BRANCH_6,
+            repository_format=RepositoryFormat.BZR_CHK_2A)
+        owner = removeSecurityProxy(branch).owner
+        login_person(owner)
+        self.addCleanup(logout)
+        branch.requestUpgrade(branch.owner)
+        with ExpectedException(
+            UpgradePending,
+            'An upgrade is already in progress for branch'
+            ' lp://dev/~person-name.*junk/branch.*.'):
+            branch.checkUpgrade()
+
     def test_needsUpgrading_branch_format_unrecognized(self):
         # A branch has a needs_upgrading attribute that returns whether or not
         # a branch needs to be upgraded or not.  If the format is
@@ -639,6 +675,17 @@
             repository_format=RepositoryFormat.BZR_CHK_2A)
         self.assertFalse(branch.needs_upgrading)
 
+    def test_checkUpgrade_branch_format_upgrade_not_needed(self):
+        # If a branch is up-to-date, checkUpgrade raises AlreadyLatestFormat
+        branch = self.factory.makePersonalBranch(
+            branch_format=BranchFormat.BZR_BRANCH_8,
+            repository_format=RepositoryFormat.BZR_CHK_2A)
+        with ExpectedException(
+            AlreadyLatestFormat,
+            'Branch lp://dev/~person-name.*junk/branch.* is in the latest'
+            ' format, so it cannot be upgraded.'):
+            branch.checkUpgrade()
+
     def test_needsUpgrading_branch_format_upgrade_needed(self):
         # A branch has a needs_upgrading attribute that returns whether or not
         # a branch needs to be upgraded or not.  If a branch doesn't support
@@ -691,18 +738,19 @@
 
     def test_requestUpgrade_no_upgrade_needed(self):
         # If a branch doesn't need to be upgraded, requestUpgrade raises an
-        # AssertionError.
+        # AlreadyLatestFormat.
         branch = self.factory.makeAnyBranch(
             branch_format=BranchFormat.BZR_BRANCH_8,
             repository_format=RepositoryFormat.BZR_CHK_2A)
         owner = removeSecurityProxy(branch).owner
         login_person(owner)
         self.addCleanup(logout)
-        self.assertRaises(AssertionError, branch.requestUpgrade, branch.owner)
+        self.assertRaises(
+            AlreadyLatestFormat, branch.requestUpgrade, branch.owner)
 
     def test_requestUpgrade_upgrade_pending(self):
         # If there is a pending upgrade already requested, requestUpgrade
-        # raises an AssertionError.
+        # raises an UpgradePending.
         branch = self.factory.makeAnyBranch(
             branch_format=BranchFormat.BZR_BRANCH_6)
         owner = removeSecurityProxy(branch).owner
@@ -710,7 +758,7 @@
         self.addCleanup(logout)
         branch.requestUpgrade(branch.owner)
 
-        self.assertRaises(AssertionError, branch.requestUpgrade, branch.owner)
+        self.assertRaises(UpgradePending, branch.requestUpgrade, branch.owner)
 
     def test_upgradePending(self):
         # If there is a BranchUpgradeJob pending for the branch, return True.

=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
--- lib/lp/code/model/tests/test_branchjob.py	2011-08-22 18:45:27 +0000
+++ lib/lp/code/model/tests/test_branchjob.py	2011-08-22 18:45:28 +0000
@@ -52,6 +52,7 @@
     BranchSubscriptionNotificationLevel,
     CodeReviewNotificationLevel,
     )
+from lp.code.errors import AlreadyLatestFormat
 from lp.code.interfaces.branchjob import (
     IBranchDiffJob,
     IBranchJob,
@@ -307,6 +308,8 @@
             'Bazaar-NG Knit Repository Format 1')
 
         job = BranchUpgradeJob.create(db_branch, self.factory.makePerson())
+
+        dbuser = config.launchpad.dbuser
         self.becomeDbUser(config.upgrade_branches.dbuser)
         with TransactionFreeOperation.require():
             job.run()
@@ -315,16 +318,17 @@
             new_branch.repository._format.get_format_string(),
             'Bazaar repository format 2a (needs bzr 1.16 or later)\n')
 
+        self.becomeDbUser(dbuser)
         self.assertFalse(db_branch.needs_upgrading)
 
     def test_needs_no_upgrading(self):
-        # Branch upgrade job creation should raise an AssertionError if the
-        # branch does not need to be upgraded.
+        # Branch upgrade job creation should raise an AlreadyLatestFormat if
+        # the branch does not need to be upgraded.
         branch = self.factory.makeAnyBranch(
             branch_format=BranchFormat.BZR_BRANCH_7,
             repository_format=RepositoryFormat.BZR_CHK_2A)
         self.assertRaises(
-            AssertionError, BranchUpgradeJob.create, branch,
+            AlreadyLatestFormat, BranchUpgradeJob.create, branch,
             self.factory.makePerson())
 
     def create_knit(self):