launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #13382
[Merge] lp:~sinzui/launchpad/nomination-investigation-1 into lp:launchpad
Curtis Hovey has proposed merging lp:~sinzui/launchpad/nomination-investigation-1 into lp:launchpad.
Commit message:
Replace doc/bug-nomination.txt with unittests.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~sinzui/launchpad/nomination-investigation-1/+merge/129489
Rewrite a doctest while investigating a fix timeout issue.
--------------------------------------------------------------------
QA
* None, this is just a test change.
LINT
lib/lp/bugs/interfaces/bugnomination.py
lib/lp/bugs/model/tests/test_bug.py
lib/lp/bugs/tests/test_bugnomination.py
lib/lp/bugs/tests/test_bugsupervisor_bugnomination.py
TEST
./bin/test -vvc -t Nomination lp.bugs.model.tests.test_bug
./bin/test -vvc lp.bugs.tests.test_bugnomination
./bin/test -vvc lp.bugs.tests.test_bugsupervisor_bugnomination
IMPLEMENTATION
Rewrote doc/bug-nomination.txt as unittest that I can extend to fix a bug.
About half of the doctest was already unit tested. There were three
surprises.
1. I was not aware that approving a nomination for a package approved
all the packages too. I found a bug questioning this behaviour and
mentioned it in the test's comment.
2. The "Automatic targeting of new source packages" section was a lie.
The test did not show what the narrative claimed. I think the rules
changed and the test was also changed, but it should have been deleted.
3. BugNomination does *not* implement IHasDateCreated as the interface
claimed, as revealed by the new unittests.
lib/lp/bugs/interfaces/bugnomination.py
lib/lp/bugs/model/tests/test_bug.py
lib/lp/bugs/tests/test_bugnomination.py
lib/lp/bugs/tests/test_bugsupervisor_bugnomination.py
--
https://code.launchpad.net/~sinzui/launchpad/nomination-investigation-1/+merge/129489
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/nomination-investigation-1 into lp:launchpad.
=== removed file 'lib/lp/bugs/doc/bug-nomination.txt'
--- lib/lp/bugs/doc/bug-nomination.txt 2012-08-07 02:31:56 +0000
+++ lib/lp/bugs/doc/bug-nomination.txt 1970-01-01 00:00:00 +0000
@@ -1,570 +0,0 @@
-Bug Nomination
-==============
-
-A bug supervisor can nominate a bug to be fixed in a specific
-distribution or product series. Nominations are created by
-calling IBug.addNomination.
-
- >>> from zope.component import getUtility
- >>> from zope.interface.verify import verifyClass
- >>> from zope.security.proxy import removeSecurityProxy
- >>> from lp.testing import login_person
- >>> from lp.bugs.interfaces.bug import IBugSet
- >>> from lp.bugs.interfaces.bugnomination import IBugNomination
- >>> from lp.bugs.model.bugnomination import BugNomination
- >>> from lp.registry.interfaces.distribution import IDistributionSet
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> from lp.registry.interfaces.product import IProductSet
- >>> from lp.testing.sampledata import (ADMIN_EMAIL)
- >>> login(ADMIN_EMAIL)
- >>> nominator = factory.makePerson(name='nominator')
- >>> ubuntu = getUtility(IDistributionSet).getByName("ubuntu")
- >>> ubuntu = removeSecurityProxy(ubuntu)
- >>> ubuntu.bug_supervisor = nominator
- >>> firefox = getUtility(IProductSet).getByName("firefox")
- >>> firefox = removeSecurityProxy(firefox)
- >>> firefox.bug_supervisor = nominator
- >>> ignored = login_person(nominator)
-
-The BugNomination class implements IBugNomination.
-
- >>> verifyClass(IBugNomination, BugNomination)
- True
-
- >>> bugset = getUtility(IBugSet)
- >>> bug_one = bugset.get(1)
-
- >>> ubuntu_grumpy = ubuntu.getSeries("grumpy")
- >>> personset = getUtility(IPersonSet)
- >>> nominator = personset.getByName("nominator")
-
- >>> grumpy_nomination = bug_one.addNomination(
- ... target=ubuntu_grumpy, owner=nominator)
-
-The nomination records the distro series or series for which the bug
-was nominated and the user that submitted the nomination (the "owner").
-
- >>> print grumpy_nomination.owner.name
- nominator
-
- >>> print grumpy_nomination.distroseries.fullseriesname
- Ubuntu Grumpy
-
-Let's create another nomination, this time on a product series.
-
- >>> from lp.registry.interfaces.product import IProductSet
-
- >>> firefox = getUtility(IProductSet).getByName("firefox")
-
- >>> firefox_trunk = firefox.getSeries("trunk")
-
- >>> nominator = personset.getByName("nominator")
-
- >>> firefox_ms_nomination = bug_one.addNomination(
- ... target=firefox_trunk, owner=nominator)
-
- >>> print firefox_ms_nomination.owner.name
- nominator
-
- >>> print firefox_ms_nomination.productseries.title
- Mozilla Firefox trunk series
-
-The target of a nomination can also be accessed through its target
-attribute.
-
- >>> print grumpy_nomination.target.bugtargetdisplayname
- Ubuntu Grumpy
-
- >>> print firefox_ms_nomination.target.bugtargetdisplayname
- Mozilla Firefox trunk
-
-Use IBug.canBeNominatedFor to see if a bug can be nominated for a
-particular distroseries or productseries. This will consider whether
-the bug has already been nominated for that series, or even already
-targeted to that series without a nomination, which can happen for bugs
-that were reported prior to the release management/nomination
-functionality existing.
-
- >>> ubuntu_breezy_autotest = ubuntu.getSeries("breezy-autotest")
-
- >>> bug_one.canBeNominatedFor(firefox_trunk)
- False
-
- >>> bug_one.canBeNominatedFor(ubuntu_grumpy)
- False
-
- >>> bug_one.canBeNominatedFor(ubuntu_breezy_autotest)
- True
-
-Bug five is already targeted to Ubuntu Warty, so even though it has no
-Warty nominations, it cannot be targeted to Warty.
-
- >>> bug_five = bugset.get(5)
-
- >>> def by_bugtargetdisplayname(bugtask):
- ... return bugtask.target.bugtargetdisplayname.lower()
-
- >>> tasks = sorted(bug_five.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- Mozilla Firefox
- Mozilla Firefox 1.0
- mozilla-firefox (Ubuntu Warty)
-
- >>> ubuntu_warty = ubuntu.getSeries("warty")
- >>> bug_five.canBeNominatedFor(ubuntu_warty)
- False
-
-The getNominationFor() method returns a nomination for a specific
-productseries or distroseries. If there is no nomination for the target
-provided, a NotFoundError is raised.
-
- >>> bug_one.getNominationFor(firefox_trunk)
- <BugNomination ...>
-
- >>> bug_one.getNominationFor(ubuntu_grumpy)
- <BugNomination ...>
-
- >>> bug_one.getNominationFor(ubuntu_breezy_autotest)
- Traceback (most recent call last):
- ...
- NotFoundError: ...
-
-IBug.getNominations() returns a list of all IBugNominations for a bug,
-ordered by IBugTarget.bugtargetdisplayname.
-
- >>> nominations = bug_one.getNominations()
-
- >>> [nomination.target.bugtargetdisplayname for nomination in nominations]
- [u'Mozilla Firefox 1.0', u'Mozilla Firefox trunk',
- u'Ubuntu Grumpy', u'Ubuntu Hoary']
-
-This method also accepts a target argument, for further filtering.
-
- >>> nominations = bug_one.getNominations(firefox)
-
- >>> [nomination.target.bugtargetdisplayname for nomination in nominations]
- [u'Mozilla Firefox 1.0', u'Mozilla Firefox trunk']
-
- >>> nominations = bug_one.getNominations(ubuntu)
-
- >>> [nomination.target.bugtargetdisplayname for nomination in nominations]
- [u'Ubuntu Grumpy', u'Ubuntu Hoary']
-
-
-Nomination Status
------------------
-
-A nomination is created with an initial status of "Nominated".
-Internally this state is called PROPOSED, but in the UI we display it
-as "Nominated".
-
- >>> ubuntu_breezy_autotest_nomination = bug_one.addNomination(
- ... target=ubuntu_breezy_autotest, owner=nominator)
-
- >>> print ubuntu_breezy_autotest_nomination.status.title
- Nominated
- >>> ubuntu_breezy_autotest_nomination.isProposed()
- True
- >>> ubuntu_breezy_autotest_nomination.isApproved()
- False
- >>> ubuntu_breezy_autotest_nomination.isDeclined()
- False
-
-Nomination status changes have an associated workflow. For this reason,
-setting status directly is not possible.
-
- >>> from lp.bugs.interfaces.bugnomination import BugNominationStatus
-
- >>> nomination.status = BugNominationStatus.APPROVED
- Traceback (most recent call last):
- ...
- ForbiddenAttribute: ...
-
-The status of a nomination is changed by calling either the approve() or
-decline() method. Only users with launchpad.Driver permission on the
-nomination can approve or decline it.
-
- >>> from lp.services.webapp.authorization import check_permission
- >>> from lp.services.webapp.interfaces import ILaunchBag
-
- >>> current_user = getUtility(ILaunchBag).user
-
- >>> current_user == nominator
- True
- >>> check_permission("launchpad.Driver", firefox_ms_nomination)
- False
-
- >>> firefox_ms_nomination.approve(nominator)
- Traceback (most recent call last):
- ..
- Unauthorized: ...
-
- >>> firefox_ms_nomination.decline(nominator)
- Traceback (most recent call last):
- ..
- Unauthorized: ...
-
-(Log in as an admin to set the driver.)
-
- >>> login("foo.bar@xxxxxxxxxxxxx")
-
- >>> no_privs = personset.getByName("no-priv")
- >>> firefox_ms_nomination.target.driver = no_privs
-
- >>> login("no-priv@xxxxxxxxxxxxx")
-
-
-Approving a nomination
-----------------------
-
-When a nomination is approved, the appropriate bugtask(s) are created on
-the target of the nomination and the status is set to APPROVED.
-
-For example, there are currently no bugtasks on the firefox_trunk
-productseries.
-
- >>> from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
-
- >>> params = BugTaskSearchParams(user=no_privs, bug=bug_one)
- >>> found_tasks = firefox_trunk.searchTasks(params)
- >>> found_tasks.count()
- 0
-
-When a nomination is approved, one task is created, targeted at
-firefox_trunk.
-
- >>> firefox_ms_nomination.approve(no_privs)
-
- >>> firefox_ms_nomination.isApproved()
- True
- >>> firefox_ms_nomination.isProposed()
- False
- >>> firefox_ms_nomination.isDeclined()
- False
-
- >>> found_tasks.count()
- 1
- >>> bugtask = found_tasks[0]
- >>> bugtask.target == firefox_trunk
- True
- >>> print bugtask.owner.name
- no-priv
-
-When a distribution bug nomination is approved, a task is created for
-each package the bug affects in that distro. For example, let's ensure
-bug #1 affects more than one Ubuntu package.
-
- >>> from lp.bugs.interfaces.bugtask import IBugTaskSet
-
- >>> ubuntu_tbird = ubuntu.getSourcePackage("thunderbird")
- >>> ignore = factory.makeSourcePackagePublishingHistory(
- ... distroseries=ubuntu.currentseries,
- ... sourcepackagename=ubuntu_tbird.sourcepackagename)
-
- >>> getUtility(IBugTaskSet).createTask(bug_one, no_privs, ubuntu_tbird)
- <BugTask ...>
-
- >>> tasks = sorted(
- ... bug_one.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- Mozilla Firefox
- Mozilla Firefox trunk
- mozilla-firefox (Debian)
- mozilla-firefox (Ubuntu)
- thunderbird (Ubuntu)
-
-When we approve the nomination, two more Ubuntu tasks are added for the
-Grumpy series. The user that made the decision is stored in the decider
-attribute. The date on which the decision was made is stored in the
-date_decided attribute.
-
-(Again, first we'll set the driver with an admin user, to ensure
-no_privs can actually approve the nomination.)
-
- >>> login("foo.bar@xxxxxxxxxxxxx")
- >>> grumpy_nomination.target.driver = no_privs
- >>> login("no-priv@xxxxxxxxxxxxx")
-
- >>> grumpy_nomination.date_decided is None
- True
- >>> grumpy_nomination.approve(no_privs)
- >>> print grumpy_nomination.status.title
- Approved
- >>> print grumpy_nomination.decider.name
- no-priv
- >>> grumpy_nomination.date_decided
- datetime...
-
- >>> tasks = sorted(
- ... bug_one.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- Mozilla Firefox
- Mozilla Firefox trunk
- mozilla-firefox (Debian)
- mozilla-firefox (Ubuntu Grumpy)
- mozilla-firefox (Ubuntu)
- thunderbird (Ubuntu Grumpy)
- thunderbird (Ubuntu)
-
-Let's now nominate for Warty. no_privs is the driver, so will have
-no problems.
-
- >>> ubuntu_warty = ubuntu.getSeries("warty")
- >>> login("foo.bar@xxxxxxxxxxxxx")
- >>> ubuntu_warty.driver = no_privs
- >>> login("no-priv@xxxxxxxxxxxxx")
-
- >>> warty_nomination = bug_one.addNomination(
- ... target=ubuntu_warty, owner=no_privs)
- >>> warty_nomination.approve(no_privs)
-
- >>> print warty_nomination.status.title
- Approved
- >>> print warty_nomination.decider.name
- no-priv
- >>> warty_nomination.date_decided
- datetime...
-
- >>> tasks = sorted(
- ... bug_one.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- Mozilla Firefox
- Mozilla Firefox trunk
- mozilla-firefox (Debian)
- mozilla-firefox (Ubuntu Grumpy)
- mozilla-firefox (Ubuntu Warty)
- mozilla-firefox (Ubuntu)
- thunderbird (Ubuntu Grumpy)
- thunderbird (Ubuntu Warty)
- thunderbird (Ubuntu)
-
- >>> login("foo.bar@xxxxxxxxxxxxx")
- >>> ubuntu_warty.driver = None
-
-
-Declining a nomination
-----------------------
-
-Declining a nomination simply sets its status to DECLINED. No tasks are
-created.
-
- >>> login("foo.bar@xxxxxxxxxxxxx")
- >>> ubuntu_breezy_autotest_nomination.target.driver = no_privs
- >>> login("no-priv@xxxxxxxxxxxxx")
-
- >>> ubuntu_breezy_autotest_nomination.date_decided is None
- True
- >>> print ubuntu_breezy_autotest_nomination.status.title
- Nominated
-
- >>> ubuntu_breezy_autotest_nomination.decline(no_privs)
-
- >>> print ubuntu_breezy_autotest_nomination.status.title
- Declined
-
- >>> ubuntu_breezy_autotest_nomination.isDeclined()
- True
- >>> ubuntu_breezy_autotest_nomination.isApproved()
- False
- >>> ubuntu_breezy_autotest_nomination.isProposed()
- False
- >>> print ubuntu_breezy_autotest_nomination.decider.name
- no-priv
- >>> ubuntu_breezy_autotest_nomination.date_decided
- datetime...
-
-If a nomination is declined, the bug can be re-nominated for the same target.
-The decider and date declined are reset to None.
-
- >>> bug_one.canBeNominatedFor(ubuntu_breezy_autotest)
- True
- >>> breezy_nomination = bug_one.addNomination(
- ... target=ubuntu_breezy_autotest, owner=no_privs)
- >>> ubuntu_breezy_autotest_nomination.isApproved()
- False
- >>> breezy_nomination.isDeclined()
- False
- >>> breezy_nomination.isProposed()
- True
- >>> print breezy_nomination.decider
- None
- >>> print breezy_nomination.date_decided
- None
-
-
-Automatic targeting of new source packages
-------------------------------------------
-
-If a another distribution task is added, and nomination for that
-distribution's series already exists, the nominations will be valid
-for the new task as well, and bugtasks will be created for all accepted
-ones.
-
-The nominations are per distroseries, they are not source package
-specific, so they are automatically valid for new bugtasks. What's
-important are the accepted nominations. Bug one has an accepted
-nomination for Grumpy and Warty:
-
- >>> accepted_nominations = [
- ... nomination for nomination in bug_one.getNominations(ubuntu)
- ... if nomination.isApproved()]
- >>> for nomination in accepted_nominations:
- ... print nomination.distroseries.displayname
- Grumpy
- Warty
-
-So if we create a new bugtask on evolution (Ubuntu), a task for
-evolution (Ubuntu Grumpy) and evolution (Ubuntu Warty) will be created
-automatically.
-
- >>> ubuntu_evolution = ubuntu.getSourcePackage('evolution')
- >>> getUtility(IBugTaskSet).createTask(
- ... bug_one, no_privs, ubuntu_evolution)
- <BugTask ...>
-
- >>> tasks = sorted(
- ... bug_one.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- evolution (Ubuntu Grumpy)
- evolution (Ubuntu Warty)
- evolution (Ubuntu)
- ...
-
-
-Changing the Source Package of a Targeted Bugtask
--------------------------------------------------
-
-The nomination model requires that a generic distribution task exists
-for each distroseries task. This causes some problem when renaming the
-source package on an accepted nomination. For example, if we would
-change the thunderbird package on the Grumpy task, it won't have a
-corresponding generic distribution task.
-
-The way we tie nominations to distribution series, and not to source
-packages, makes it hard to solve source package changes in a nice way.
-So what happens when a source package is changed is that we simply
-rename all other bugtasks which points to the same distribution and
-source package name. This is not ideal, but hopefully package renames
-after the bug has been targeted to a series is rare enough for this to
-be acceptable.
-
- >>> thunderbird_grumpy = tasks[-3]
- >>> thunderbird_grumpy.bugtargetname
- u'thunderbird (Ubuntu Grumpy)'
-
- >>> thunderbird_grumpy.transitionToTarget(
- ... ubuntu.getSeries('grumpy').getSourcePackage('pmount'),
- ... getUtility(ILaunchBag).user)
-
- >>> tasks = sorted(
- ... bug_one.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- evolution (Ubuntu Grumpy)
- evolution (Ubuntu Warty)
- evolution (Ubuntu)
- Mozilla Firefox
- Mozilla Firefox trunk
- mozilla-firefox (Debian)
- mozilla-firefox (Ubuntu Grumpy)
- mozilla-firefox (Ubuntu Warty)
- mozilla-firefox (Ubuntu)
- pmount (Ubuntu Grumpy)
- pmount (Ubuntu Warty)
- pmount (Ubuntu)
-
-The same is done if the distribution task's source package is changed.
-
- >>> pmount_ubuntu = tasks[-1]
- >>> pmount_ubuntu.bugtargetname
- u'pmount (Ubuntu)'
-
- >>> ubuntu_thunderbird = ubuntu.getSourcePackage('thunderbird')
- >>> pmount_ubuntu.transitionToTarget(
- ... ubuntu_thunderbird, getUtility(ILaunchBag).user)
-
- >>> tasks = sorted(
- ... bug_one.bugtasks, key=by_bugtargetdisplayname)
-
- >>> for task in tasks:
- ... print task.target.bugtargetdisplayname
- evolution (Ubuntu Grumpy)
- evolution (Ubuntu Warty)
- evolution (Ubuntu)
- Mozilla Firefox
- Mozilla Firefox trunk
- mozilla-firefox (Debian)
- mozilla-firefox (Ubuntu Grumpy)
- mozilla-firefox (Ubuntu Warty)
- mozilla-firefox (Ubuntu)
- thunderbird (Ubuntu Grumpy)
- thunderbird (Ubuntu Warty)
- thunderbird (Ubuntu)
-
-
-Bug Nomination Set
-------------------
-
-IBugNominationSet is used to fetch bug nominations by ID. This is useful
-mainly in traversal code.
-
- >>> from lp.bugs.interfaces.bugnomination import IBugNominationSet
-
- >>> getUtility(IBugNominationSet).get(1)
- <BugNomination at ...>
-
-If a nomination is not found, a NotFoundError is raised.
-
- >>> getUtility(IBugNominationSet).get(-1)
- Traceback (most recent call last):
- ...
- NotFoundError: ...
-
-
-Error Handling
---------------
-
-Trying to nominate a bug for a series for which it's already nominated
-or targeted raises a NominationError.
-
- >>> bug_one.addNomination(
- ... target=ubuntu_grumpy, owner=no_privs)
- Traceback (most recent call last):
- ..
- NominationError: ...
-
- >>> bug_one.addNomination(
- ... target=firefox_trunk, owner=no_privs)
- Traceback (most recent call last):
- ..
- NominationError: ...
-
-Nominating a bug for an obsolete distroseries raises a
-NominationSeriesObsoleteError. Let's make a new obsolete distroseries
-to demonstrate.
-
- >>> from lp.registry.interfaces.series import SeriesStatus
-
- >>> login("foo.bar@xxxxxxxxxxxxx")
- >>> ubuntu_edgy = factory.makeDistroSeries(
- ... distribution=ubuntu, version='6.10',
- ... status=SeriesStatus.OBSOLETE)
- >>> login("no-priv@xxxxxxxxxxxxx")
-
- >>> bug_one.addNomination(target=ubuntu_edgy, owner=no_privs)
- Traceback (most recent call last):
- ..
- NominationSeriesObsoleteError: ...
-
- >>> logout()
=== modified file 'lib/lp/bugs/interfaces/bugnomination.py'
--- lib/lp/bugs/interfaces/bugnomination.py 2012-01-01 02:58:52 +0000
+++ lib/lp/bugs/interfaces/bugnomination.py 2012-10-12 18:08:24 +0000
@@ -47,7 +47,6 @@
)
from lp import _
-from lp.app.interfaces.launchpad import IHasDateCreated
from lp.app.validators.validation import can_be_nominated_for_series
from lp.bugs.interfaces.bug import IBug
from lp.bugs.interfaces.bugtarget import IBugTarget
@@ -101,7 +100,7 @@
""")
-class IBugNomination(IHasBug, IHasOwner, IHasDateCreated):
+class IBugNomination(IHasBug, IHasOwner):
"""A nomination for a bug to be fixed in a specific series.
A nomination can apply to an IDistroSeries or an IProductSeries.
=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
--- lib/lp/bugs/model/tests/test_bug.py 2012-10-08 01:02:13 +0000
+++ lib/lp/bugs/model/tests/test_bug.py 2012-10-12 18:08:24 +0000
@@ -70,6 +70,31 @@
nomination = bug.getNominationFor(sourcepackage)
self.assertEqual(series, nomination.target)
+ def makeManyNominations(self):
+ target = self.factory.makeSourcePackage()
+ series = target.distroseries
+ with person_logged_in(series.distribution.owner):
+ nomination = self.factory.makeBugNomination(target=target)
+ bug = nomination.bug
+ other_series = self.factory.makeProductSeries()
+ other_target = other_series.product
+ self.factory.makeBugTask(bug=bug, target=other_target)
+ with person_logged_in(other_target.owner):
+ other_nomination = bug.addNomination(
+ other_target.owner, other_series)
+ return bug, [nomination, other_nomination]
+
+ def test_getNominations(self):
+ # The getNominations() method returns all the nominations for the bug.
+ bug, nominations = self.makeManyNominations()
+ self.assertContentEqual(nominations, bug.getNominations())
+
+ def test_getNominations_with_target(self):
+ # The target argument filters the nominations to just one pillar.
+ bug, nominations = self.makeManyNominations()
+ pillar = nominations[0].target.pillar
+ self.assertContentEqual([nominations[0]], bug.getNominations(pillar))
+
def test_markAsDuplicate_None(self):
# Calling markAsDuplicate(None) on a bug that is not currently a
# duplicate works correctly, and does not raise an AttributeError.
=== modified file 'lib/lp/bugs/tests/test_bugnomination.py'
--- lib/lp/bugs/tests/test_bugnomination.py 2012-08-08 07:22:51 +0000
+++ lib/lp/bugs/tests/test_bugnomination.py 2012-10-12 18:08:24 +0000
@@ -5,16 +5,200 @@
__metaclass__ = type
+from zope.component import getUtility
+
+from lp.app.errors import NotFoundError
+from lp.bugs.interfaces.bugnomination import (
+ BugNominationStatusError,
+ BugNominationStatus,
+ IBugNomination,
+ IBugNominationSet,
+ )
from lp.soyuz.interfaces.publishing import PackagePublishingStatus
from lp.testing import (
celebrity_logged_in,
login,
logout,
+ person_logged_in,
TestCaseWithFactory,
)
from lp.testing.layers import DatabaseFunctionalLayer
+class BugNominationTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_implementation(self):
+ # BugNomination implements IBugNomination.
+ target = self.factory.makeSourcePackage()
+ series = target.distroseries
+ bug = self.factory.makeBug(target=target.distribution_sourcepackage)
+ with person_logged_in(series.distribution.owner):
+ nomination = bug.addNomination(series.distribution.owner, series)
+ self.assertProvides(nomination, IBugNomination)
+ self.assertEqual(series.distribution.owner, nomination.owner)
+ self.assertEqual(bug, nomination.bug)
+ self.assertEqual(series, nomination.distroseries)
+ self.assertIsNone(nomination.productseries)
+ self.assertEqual(BugNominationStatus.PROPOSED, nomination.status)
+ self.assertIsNone(nomination.date_decided)
+ self.assertEqual('UTC', nomination.date_created.tzname())
+
+ def test_target_distroseries(self):
+ # The target property returns the distroseries if it is not None.
+ target = self.factory.makeSourcePackage()
+ series = target.distroseries
+ with person_logged_in(series.distribution.owner):
+ nomination = self.factory.makeBugNomination(target=target)
+ self.assertEqual(series, nomination.distroseries)
+ self.assertEqual(series, nomination.target)
+
+ def test_target_productseries(self):
+ # The target property returns the productseries if it is not None.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ self.assertEqual(series, nomination.productseries)
+ self.assertEqual(series, nomination.target)
+
+ def test_status_proposed(self):
+ # isProposed is True when the status is PROPOSED.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ self.assertEqual(BugNominationStatus.PROPOSED, nomination.status)
+ self.assertIs(True, nomination.isProposed())
+ self.assertIs(False, nomination.isDeclined())
+ self.assertIs(False, nomination.isApproved())
+
+ def test_status_declined(self):
+ # isDeclined is True when the status is DECLINED.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ nomination.decline(series.product.owner)
+ self.assertEqual(BugNominationStatus.DECLINED, nomination.status)
+ self.assertIs(True, nomination.isDeclined())
+ self.assertIs(False, nomination.isProposed())
+ self.assertIs(False, nomination.isApproved())
+
+ def test_status_approved(self):
+ # isApproved is True when the status is APPROVED.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ nomination.approve(series.product.owner)
+ self.assertEqual(BugNominationStatus.APPROVED, nomination.status)
+ self.assertIs(True, nomination.isApproved())
+ self.assertIs(False, nomination.isDeclined())
+ self.assertIs(False, nomination.isProposed())
+
+ def test_decline(self):
+ # The decline method updates the status and other data.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ bug_tasks = nomination.bug.bugtasks
+ nomination.decline(series.product.owner)
+ self.assertEqual(BugNominationStatus.DECLINED, nomination.status)
+ self.assertIsNotNone(nomination.date_decided)
+ self.assertEqual(series.product.owner, nomination.decider)
+ self.assertContentEqual(bug_tasks, nomination.bug.bugtasks)
+
+ def test_decline_error(self):
+ # A nomination cannot be declined if it is approved.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ nomination.approve(series.product.owner)
+ self.assertRaises(
+ BugNominationStatusError,
+ nomination.decline, series.product.owner)
+
+ def test_approve_productseries(self):
+ # Approving a product nomination creates a productseries bug task.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ bug_tasks = nomination.bug.bugtasks
+ nomination.approve(series.product.owner)
+ self.assertEqual(BugNominationStatus.APPROVED, nomination.status)
+ self.assertIsNotNone(nomination.date_decided)
+ self.assertEqual(series.product.owner, nomination.decider)
+ expected_targets = [bt.target for bt in bug_tasks] + [series]
+ self.assertContentEqual(
+ expected_targets, [bt.target for bt in nomination.bug.bugtasks])
+
+ def test_approve_distroseries_source_package(self):
+ # Approving a package nomination creates a distroseries
+ # source package bug task.
+ target = self.factory.makeSourcePackage()
+ series = target.distroseries
+ with person_logged_in(series.distribution.owner):
+ nomination = self.factory.makeBugNomination(target=target)
+ bug_tasks = nomination.bug.bugtasks
+ nomination.approve(series.distribution.owner)
+ self.assertEqual(BugNominationStatus.APPROVED, nomination.status)
+ self.assertIsNotNone(nomination.date_decided)
+ self.assertEqual(series.distribution.owner, nomination.decider)
+ expected_targets = [bt.target for bt in bug_tasks] + [target]
+ self.assertContentEqual(
+ expected_targets, [bt.target for bt in nomination.bug.bugtasks])
+
+ def test_approve_distroseries_source_package_many(self):
+ # Approving a package nomination creates a distroseries
+ # source package bug task for each affect package in the same distro.
+ # See bug 110195 which argues this is wrong.
+ target = self.factory.makeSourcePackage()
+ series = target.distroseries
+ target2 = self.factory.makeSourcePackage(distroseries=series)
+ bug = self.factory.makeBug(target=target.distribution_sourcepackage)
+ self.factory.makeBugTask(
+ bug=bug, target=target2.distribution_sourcepackage)
+ bug_tasks = bug.bugtasks
+ with person_logged_in(series.distribution.owner):
+ nomination = self.factory.makeBugNomination(bug=bug, target=target)
+ nomination.approve(series.distribution.owner)
+ expected_targets = [bt.target for bt in bug_tasks] + [target, target2]
+ self.assertContentEqual(
+ expected_targets, [bt.target for bt in bug.bugtasks])
+
+ def test_approve_twice(self):
+ # Approving a nomination twice is a no-op.
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ nomination.approve(series.product.owner)
+ self.assertEqual(BugNominationStatus.APPROVED, nomination.status)
+ date_decided = nomination.date_decided
+ self.assertIsNotNone(date_decided)
+ self.assertEqual(series.product.owner, nomination.decider)
+ with celebrity_logged_in('admin') as admin:
+ nomination.approve(admin)
+ self.assertEqual(date_decided, nomination.date_decided)
+ self.assertEqual(series.product.owner, nomination.decider)
+
+ def test_approve_distroseries_source_package_then_retarget(self):
+ # Retargeting a bugtarget with and approved nomination also
+ # retargets the master bug target.
+ target = self.factory.makeSourcePackage()
+ series = target.distroseries
+ with person_logged_in(series.distribution.owner):
+ nomination = self.factory.makeBugNomination(target=target)
+ nomination.approve(series.distribution.owner)
+ target2 = self.factory.makeSourcePackage(
+ distroseries=series, publish=True)
+ product_target = nomination.bug.bugtasks[0].target
+ expected_targets = [
+ product_target, target2, target2.distribution_sourcepackage]
+ bug_task = nomination.bug.bugtasks[-1]
+ with person_logged_in(series.distribution.owner):
+ bug_task.transitionToTarget(target2, series.distribution.owner)
+ self.assertContentEqual(
+ expected_targets, [bt.target for bt in nomination.bug.bugtasks])
+
+
class CanBeNominatedForTestMixin:
"""Test case mixin for IBug.canBeNominatedFor."""
@@ -218,3 +402,19 @@
self.assertFalse(nomination.canApprove(self.factory.makePerson()))
self.assertTrue(nomination.canApprove(package_perm.person))
self.assertTrue(nomination.canApprove(comp_perm.person))
+
+
+class BugNominationSetTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_get(self):
+ series = self.factory.makeProductSeries()
+ with person_logged_in(series.product.owner):
+ nomination = self.factory.makeBugNomination(target=series)
+ bug_nomination_set = getUtility(IBugNominationSet)
+ self.assertEqual(nomination, bug_nomination_set.get(nomination.id))
+
+ def test_get_none(self):
+ bug_nomination_set = getUtility(IBugNominationSet)
+ self.assertRaises(NotFoundError, bug_nomination_set.get, -1)
=== modified file 'lib/lp/bugs/tests/test_bugsupervisor_bugnomination.py'
--- lib/lp/bugs/tests/test_bugsupervisor_bugnomination.py 2012-08-08 07:22:51 +0000
+++ lib/lp/bugs/tests/test_bugsupervisor_bugnomination.py 2012-10-12 18:08:24 +0000
@@ -5,8 +5,13 @@
__metaclass__ = type
-from lp.bugs.interfaces.bugnomination import NominationError
+from lp.bugs.interfaces.bugnomination import (
+ NominationError,
+ NominationSeriesObsoleteError,
+ )
+from lp.registry.interfaces.series import SeriesStatus
from lp.testing import (
+ celebrity_logged_in,
login,
login_person,
logout,
@@ -47,6 +52,14 @@
self.bug.addNomination(self.bug_supervisor, self.series)
self.assertTrue(len(self.bug.getNominations()), 1)
+ def test_bugsupervisor_addNominationFor_with_existing_nomination(self):
+ # A bug cannot be nominated twice for the same series.
+ login_person(self.bug_supervisor)
+ self.bug.addNomination(self.bug_supervisor, self.series)
+ self.assertTrue(len(self.bug.getNominations()), 1)
+ self.assertRaises(NominationError,
+ self.bug.addNomination, self.user, self.series)
+
def test_owner_addNominationFor_series(self):
# A bug may be nominated for a series of a product with an
# exisiting task by the product's owner.
@@ -82,3 +95,11 @@
self.bug.addTask(self.bug_supervisor, self.distro)
self.milestone = self.factory.makeMilestone(
distribution=self.distro)
+
+ def test_bugsupervisor_addNominationFor_with_obsolete_distroseries(self):
+ # A bug cannot be nominated for an obsolete series.
+ with celebrity_logged_in('admin'):
+ self.series.status = SeriesStatus.OBSOLETE
+ login_person(self.bug_supervisor)
+ self.assertRaises(NominationSeriesObsoleteError,
+ self.bug.addNomination, self.user, self.series)
Follow ups