launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01496
lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085 into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085 into lp:launchpad with lp:~allenap/launchpad/wire-up-filter-subs-bug-655567 as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers): code
Related bugs:
#659085 getBugNotificationsRecipients() has been superceded
https://bugs.launchpad.net/bugs/659085
This is a big branch, but fear not, most of it is moving code and
deletions. I shall guide you though the dark forest...
- tests/test_bugtask_0.py, tests/test_bugtask_1.py and
tests/test_bugtask_status.py have been folded into
tests/test_bugtask.py. The first two were left over from the
automated tree migration.
- test_bugtask_0.py contained one doctest-in-a-doctring which was
rewritten as a unittest (it's *very* short). It's still called
test_open_and_resolved_statuses.
- tests/test_bugtask.py and tests/test_bugtask_status.py have moved
into model/tests.
- Bug.getStructuralSubscribers() has moved to BugTaskSet. It now
accepts a list of bugtasks instead of getting them from
self.bugtasks, and gets the store to use with IStore(Person) rather
than Store.of(self).
- TestBugStructuralSubscribers has moved from model/tests/test_bug.py
to model/tests/test_bugtask.py, and is now called.
TestGetStructuralSubscribers.
- A couple of functions have been changed to use
BugTaskSet.getStructuralSubscribers() via the utility.
- getBugNotificationRecipients() has been removed, along with its
associated tests.
I'm sorry it got a bit long. The dependencies of each change cascaded
somewhat.
--
https://code.launchpad.net/~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085/+merge/38325
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085 into lp:launchpad.
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2010-10-13 13:11:13 +0000
+++ lib/lp/bugs/configure.zcml 2010-10-06 18:53:53 +0000
@@ -706,7 +706,6 @@
getSubscribersForPerson
indexed_messages
getAlsoNotifiedSubscribers
- getStructuralSubscribers
getBugWatch
canBeNominatedFor
getNominationFor
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-10-13 13:11:13 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-10-13 13:11:15 +0000
@@ -491,12 +491,6 @@
from duplicates.
"""
- def getStructuralSubscribers(recipients=None, level=None):
- """Return `IPerson`s subscribed to this bug's targets.
-
- This takes into account bug subscription filters.
- """
-
def getSubscriptionsFromDuplicates():
"""Return IBugSubscriptions subscribed from dupes of this bug."""
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-09-21 09:37:06 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2010-10-13 13:11:15 +0000
@@ -1523,6 +1523,12 @@
def getOpenBugTasksPerProduct(user, products):
"""Return open bugtask count for multiple products."""
+ def getStructuralSubscribers(bugtasks, recipients=None, level=None):
+ """Return `IPerson`s subscribed to the given bug tasks.
+
+ This takes into account bug subscription filters.
+ """
+
def valid_remote_bug_url(value):
"""Verify that the URL is to a bug to a known bug tracker."""
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2010-10-13 13:11:13 +0000
+++ lib/lp/bugs/model/bug.py 2010-10-13 13:11:15 +0000
@@ -175,9 +175,6 @@
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.series import SeriesStatus
from lp.registry.interfaces.sourcepackage import ISourcePackage
-from lp.registry.interfaces.structuralsubscription import (
- IStructuralSubscriptionTarget,
- )
from lp.registry.model.mentoringoffer import MentoringOffer
from lp.registry.model.person import (
Person,
@@ -917,8 +914,9 @@
# XXX: RobertCollins 2010-09-22 bug=374777: This SQL(...) is a
# hack; it does not seem to be possible to express DISTINCT ON
# with Storm.
- (SQL("DISTINCT ON (Person.name, BugSubscription.person) 0 AS ignore"),
- # return people and subscribptions
+ (SQL("DISTINCT ON (Person.name, BugSubscription.person) "
+ "0 AS ignore"),
+ # Return people and subscriptions
Person, BugSubscription),
# For this bug or its duplicates
Or(
@@ -969,8 +967,8 @@
# Structural subscribers.
also_notified_subscribers.update(
- self.getStructuralSubscribers(
- recipients=recipients, level=level))
+ getUtility(IBugTaskSet).getStructuralSubscribers(
+ self.bugtasks, recipients=recipients, level=level))
# Direct subscriptions always take precedence over indirect
# subscriptions.
@@ -982,58 +980,6 @@
(also_notified_subscribers - direct_subscribers),
key=lambda x: removeSecurityProxy(x).displayname)
- def getStructuralSubscribers(self, recipients=None, level=None):
- """See `IBug`. """
- query_arguments = []
- for bugtask in self.bugtasks:
- if IStructuralSubscriptionTarget.providedBy(bugtask.target):
- query_arguments.append((bugtask.target, bugtask))
- if bugtask.target.parent_subscription_target is not None:
- query_arguments.append(
- (bugtask.target.parent_subscription_target, bugtask))
- if ISourcePackage.providedBy(bugtask.target):
- # Distribution series bug tasks with a package have the source
- # package set as their target, so we add the distroseries
- # explicitly to the set of subscription targets.
- query_arguments.append((bugtask.distroseries, bugtask))
- if bugtask.milestone is not None:
- query_arguments.append((bugtask.milestone, bugtask))
-
- if len(query_arguments) == 0:
- return EmptyResultSet()
-
- if level is None:
- # If level is not specified, default to NOTHING so that all
- # subscriptions are found. XXX: Perhaps this should go in
- # getSubscriptionsForBugTask()?
- level = BugNotificationLevel.NOTHING
-
- # Build the query.
- union = lambda left, right: left.union(right)
- queries = (
- target.getSubscriptionsForBugTask(bugtask, level)
- for target, bugtask in query_arguments)
- subscriptions = reduce(union, queries)
-
- # Pull all the subscriptions in.
- subscriptions = list(subscriptions)
-
- # Prepare a query for the subscribers.
- subscribers = Store.of(self).find(
- Person, Person.id.is_in(
- subscription.subscriberID
- for subscription in subscriptions))
-
- if recipients is not None:
- # We need to process subscriptions, so pull all the subscribes
- # into the cache, then update recipients with the subscriptions.
- subscribers = list(subscribers)
- for subscription in subscriptions:
- recipients.addStructuralSubscriber(
- subscription.subscriber, subscription.target)
-
- return subscribers
-
def getBugNotificationRecipients(self, duplicateof=None, old_bug=None,
level=None,
include_master_dupe_subscribers=False):
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2010-09-23 10:27:11 +0000
+++ lib/lp/bugs/model/bugtask.py 2010-10-13 13:11:15 +0000
@@ -127,6 +127,7 @@
)
from lp.bugs.model.bugnomination import BugNomination
from lp.bugs.model.bugsubscription import BugSubscription
+from lp.registry.enum import BugNotificationLevel
from lp.registry.interfaces.distribution import (
IDistribution,
IDistributionSet,
@@ -155,6 +156,9 @@
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.registry.interfaces.sourcepackage import ISourcePackage
from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
+from lp.registry.interfaces.structuralsubscription import (
+ IStructuralSubscriptionTarget,
+ )
from lp.registry.model.pillar import pillar_sort_key
from lp.registry.model.sourcepackagename import SourcePackageName
from lp.services.propertycache import IPropertyCache
@@ -1947,7 +1951,7 @@
query = " AND ".join(extra_clauses)
if not decorators:
- decorator = lambda x:x
+ decorator = lambda x: x
else:
def decorator(obj):
for decor in decorators:
@@ -2829,3 +2833,55 @@
counts.append(package_counts)
return counts
+
+ def getStructuralSubscribers(self, bugtasks, recipients=None, level=None):
+ """See `IBugTaskSet`."""
+ query_arguments = []
+ for bugtask in bugtasks:
+ if IStructuralSubscriptionTarget.providedBy(bugtask.target):
+ query_arguments.append((bugtask.target, bugtask))
+ if bugtask.target.parent_subscription_target is not None:
+ query_arguments.append(
+ (bugtask.target.parent_subscription_target, bugtask))
+ if ISourcePackage.providedBy(bugtask.target):
+ # Distribution series bug tasks with a package have the source
+ # package set as their target, so we add the distroseries
+ # explicitly to the set of subscription targets.
+ query_arguments.append((bugtask.distroseries, bugtask))
+ if bugtask.milestone is not None:
+ query_arguments.append((bugtask.milestone, bugtask))
+
+ if len(query_arguments) == 0:
+ return EmptyResultSet()
+
+ if level is None:
+ # If level is not specified, default to NOTHING so that all
+ # subscriptions are found.
+ level = BugNotificationLevel.NOTHING
+
+ # Build the query.
+ union = lambda left, right: left.union(right)
+ queries = (
+ target.getSubscriptionsForBugTask(bugtask, level)
+ for target, bugtask in query_arguments)
+ subscriptions = reduce(union, queries)
+
+ # Pull all the subscriptions in.
+ subscriptions = list(subscriptions)
+
+ # Prepare a query for the subscribers.
+ from lp.registry.model.person import Person
+ subscribers = IStore(Person).find(
+ Person, Person.id.is_in(
+ subscription.subscriberID
+ for subscription in subscriptions))
+
+ if recipients is not None:
+ # We need to process subscriptions, so pull all the subscribes into
+ # the cache, then update recipients with the subscriptions.
+ subscribers = list(subscribers)
+ for subscription in subscriptions:
+ recipients.addStructuralSubscriber(
+ subscription.subscriber, subscription.target)
+
+ return subscribers
=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
--- lib/lp/bugs/model/tests/test_bug.py 2010-10-13 13:11:13 +0000
+++ lib/lp/bugs/model/tests/test_bug.py 2010-10-13 13:11:15 +0000
@@ -5,11 +5,7 @@
__metaclass__ = type
-from storm.store import ResultSet
-
from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
-from lp.registry.enum import BugNotificationLevel
from lp.registry.interfaces.person import PersonVisibility
from lp.registry.model.structuralsubscription import StructuralSubscription
from lp.testing import (
@@ -17,7 +13,6 @@
person_logged_in,
TestCaseWithFactory,
)
-from lp.testing.matchers import StartsWith
class TestBug(TestCaseWithFactory):
@@ -130,84 +125,3 @@
dupe_bug.subscribe(team, member)
dupe_bug.markAsDuplicate(bug)
self.assertTrue(team in bug.getSubscribersFromDuplicates())
-
-
-class TestBugStructuralSubscribers(TestCaseWithFactory):
-
- layer = DatabaseFunctionalLayer
-
- def test_getStructuralSubscribers_no_subscribers(self):
- # If there are no subscribers for any of the bug's targets then no
- # subscribers will be returned by getStructuralSubscribers().
- product = self.factory.makeProduct()
- bug = self.factory.makeBug(product=product)
- subscribers = bug.getStructuralSubscribers()
- self.assertIsInstance(subscribers, ResultSet)
- self.assertEqual([], list(subscribers))
-
- def test_getStructuralSubscribers_single_target(self):
- # Subscribers for any of the bug's targets are returned.
- subscriber = self.factory.makePerson()
- login_person(subscriber)
- product = self.factory.makeProduct()
- product.addBugSubscription(subscriber, subscriber)
- bug = self.factory.makeBug(product=product)
- self.assertEqual([subscriber], list(bug.getStructuralSubscribers()))
-
- def test_getStructuralSubscribers_multiple_targets(self):
- # Subscribers for any of the bug's targets are returned.
- actor = self.factory.makePerson()
- login_person(actor)
-
- subscriber1 = self.factory.makePerson()
- subscriber2 = self.factory.makePerson()
-
- product1 = self.factory.makeProduct(owner=actor)
- product1.addBugSubscription(subscriber1, subscriber1)
- product2 = self.factory.makeProduct(owner=actor)
- product2.addBugSubscription(subscriber2, subscriber2)
-
- bug = self.factory.makeBug(product=product1)
- bug.addTask(actor, product2)
-
- subscribers = bug.getStructuralSubscribers()
- self.assertIsInstance(subscribers, ResultSet)
- self.assertEqual(set([subscriber1, subscriber2]), set(subscribers))
-
- def test_getStructuralSubscribers_recipients(self):
- # If provided, getStructuralSubscribers() calls the appropriate
- # methods on a BugNotificationRecipients object.
- subscriber = self.factory.makePerson()
- login_person(subscriber)
- product = self.factory.makeProduct()
- product.addBugSubscription(subscriber, subscriber)
- bug = self.factory.makeBug(product=product)
- recipients = BugNotificationRecipients()
- subscribers = bug.getStructuralSubscribers(recipients=recipients)
- # The return value is a list only when populating recipients.
- self.assertIsInstance(subscribers, list)
- self.assertEqual([subscriber], recipients.getRecipients())
- reason, header = recipients.getReason(subscriber)
- self.assertThat(
- reason, StartsWith(
- u"You received this bug notification because "
- u"you are subscribed to "))
- self.assertThat(header, StartsWith(u"Subscriber "))
-
- def test_getStructuralSubscribers_level(self):
- # getStructuralSubscribers() respects the given level.
- subscriber = self.factory.makePerson()
- login_person(subscriber)
- product = self.factory.makeProduct()
- subscription = product.addBugSubscription(subscriber, subscriber)
- subscription.bug_notification_level = BugNotificationLevel.METADATA
- bug = self.factory.makeBug(product=product)
- self.assertEqual(
- [subscriber], list(
- bug.getStructuralSubscribers(
- level=BugNotificationLevel.METADATA)))
- subscription.bug_notification_level = BugNotificationLevel.METADATA
- self.assertEqual(
- [], list(
- bug.getStructuralSubscribers(
- level=BugNotificationLevel.COMMENTS)))
=== renamed file 'lib/lp/bugs/tests/test_bugtask.py' => 'lib/lp/bugs/model/tests/test_bugtask.py'
--- lib/lp/bugs/tests/test_bugtask.py 2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/model/tests/test_bugtask.py 2010-10-13 13:11:15 +0000
@@ -8,31 +8,55 @@
import unittest
from lazr.lifecycle.snapshot import Snapshot
+from storm.store import ResultSet
from zope.component import getUtility
from zope.interface import providedBy
+from zope.security.proxy import removeSecurityProxy
+from canonical.database.sqlbase import flush_database_updates
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
from canonical.launchpad.searchbuilder import (
all,
any,
)
+from canonical.launchpad.testing import systemdocs
+from canonical.launchpad.webapp.interfaces import ILaunchBag
from canonical.testing.layers import (
DatabaseFunctionalLayer,
+ LaunchpadFunctionalLayer,
LaunchpadZopelessLayer,
)
+from lp.app.enums import ServiceUsage
+from lp.bugs.interfaces.bug import IBugSet
from lp.bugs.interfaces.bugtarget import IBugTarget
from lp.bugs.interfaces.bugtask import (
BugTaskImportance,
BugTaskSearchParams,
BugTaskStatus,
+ IBugTaskSet,
+ IUpstreamBugTask,
+ RESOLVED_BUGTASK_STATUSES,
+ UNRESOLVED_BUGTASK_STATUSES,
)
+from lp.bugs.interfaces.bugwatch import IBugWatchSet
+from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
from lp.bugs.model.bugtask import build_tag_search_clause
+from lp.bugs.tests.bug import (
+ create_old_bug,
+ sync_bugtasks,
+ )
from lp.hardwaredb.interfaces.hwdb import (
HWBus,
IHWDeviceSet,
)
+from lp.registry.enum import BugNotificationLevel
from lp.registry.interfaces.distribution import IDistributionSet
-from lp.registry.interfaces.person import IPerson, IPersonSet
+from lp.registry.interfaces.person import (
+ IPerson,
+ IPersonSet,
+ )
+from lp.registry.interfaces.product import IProductSet
+from lp.registry.interfaces.projectgroup import IProjectGroupSet
from lp.testing import (
ANONYMOUS,
login,
@@ -42,6 +66,8 @@
TestCase,
TestCaseWithFactory,
)
+from lp.testing.factory import LaunchpadObjectFactory
+from lp.testing.matchers import StartsWith
class TestBugTaskDelta(TestCaseWithFactory):
@@ -892,7 +918,7 @@
self.assertEqual(2, tasks.count())
# Cache in the storm cache the account->person lookup so its not
# distorting what we're testing.
- _ = IPerson(person.account, None)
+ IPerson(person.account, None)
# One query and only one should be issued to get the tasks, bugs and
# allow access to getConjoinedMaster attribute - an attribute that
# triggers a permission check (nb: id does not trigger such a check)
@@ -909,8 +935,427 @@
self.assertEqual([task1], list(default_result))
+class BugTaskSearchBugsElsewhereTest(unittest.TestCase):
+ """Tests for searching bugs filtering on related bug tasks.
+
+ It also acts as a helper class, which makes related doctests more
+ readable, since they can use methods from this class.
+ """
+ layer = DatabaseFunctionalLayer
+
+ def __init__(self, methodName='runTest', helper_only=False):
+ """If helper_only is True, set up it only as a helper class."""
+ if not helper_only:
+ unittest.TestCase.__init__(self, methodName=methodName)
+
+ def setUp(self):
+ login(ANONYMOUS)
+
+ def tearDown(self):
+ logout()
+
+ def _getBugTaskByTarget(self, bug, target):
+ """Return a bug's bugtask for the given target."""
+ for bugtask in bug.bugtasks:
+ if bugtask.target == target:
+ return bugtask
+ else:
+ raise AssertionError(
+ "Didn't find a %s task on bug %s." % (
+ target.bugtargetname, bug.id))
+
+ def setUpBugsResolvedUpstreamTests(self):
+ """Modify some bugtasks to match the resolved upstream filter."""
+ bugset = getUtility(IBugSet)
+ productset = getUtility(IProductSet)
+ firefox = productset.getByName("firefox")
+ thunderbird = productset.getByName("thunderbird")
+
+ # Mark an upstream task on bug #1 "Fix Released"
+ bug_one = bugset.get(1)
+ firefox_upstream = self._getBugTaskByTarget(bug_one, firefox)
+ self.assertEqual(
+ ServiceUsage.LAUNCHPAD,
+ firefox_upstream.product.bug_tracking_usage)
+ self.old_firefox_status = firefox_upstream.status
+ firefox_upstream.transitionToStatus(
+ BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
+ self.firefox_upstream = firefox_upstream
+
+ # Mark an upstream task on bug #9 "Fix Committed"
+ bug_nine = bugset.get(9)
+ thunderbird_upstream = self._getBugTaskByTarget(bug_nine, thunderbird)
+ self.old_thunderbird_status = thunderbird_upstream.status
+ thunderbird_upstream.transitionToStatus(
+ BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user)
+ self.thunderbird_upstream = thunderbird_upstream
+
+ # Add a watch to a Debian bug for bug #2, and mark the task Fix
+ # Released.
+ bug_two = bugset.get(2)
+ bugwatchset = getUtility(IBugWatchSet)
+
+ # Get a debbugs watch.
+ watch_debbugs_327452 = bugwatchset.get(9)
+ self.assertEquals(watch_debbugs_327452.bugtracker.name, "debbugs")
+ self.assertEquals(watch_debbugs_327452.remotebug, "327452")
+
+ # Associate the watch to a Fix Released task.
+ debian = getUtility(IDistributionSet).getByName("debian")
+ debian_firefox = debian.getSourcePackage("mozilla-firefox")
+ bug_two_in_debian_firefox = self._getBugTaskByTarget(
+ bug_two, debian_firefox)
+ bug_two_in_debian_firefox.bugwatch = watch_debbugs_327452
+ bug_two_in_debian_firefox.transitionToStatus(
+ BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
+
+ flush_database_updates()
+
+ def tearDownBugsElsewhereTests(self):
+ """Resets the modified bugtasks to their original statuses."""
+ self.firefox_upstream.transitionToStatus(
+ self.old_firefox_status, getUtility(ILaunchBag).user)
+ self.thunderbird_upstream.transitionToStatus(
+ self.old_thunderbird_status, getUtility(ILaunchBag).user)
+ flush_database_updates()
+
+ def assertBugTaskIsPendingBugWatchElsewhere(self, bugtask):
+ """Assert the bugtask is pending a bug watch elsewhere.
+
+ Pending a bugwatch elsewhere means that at least one of the bugtask's
+ related task's target isn't using Malone, and that
+ related_bugtask.bugwatch is None.
+ """
+ non_malone_using_bugtasks = [
+ related_task for related_task in bugtask.related_tasks
+ if not related_task.target_uses_malone]
+ pending_bugwatch_bugtasks = [
+ related_bugtask for related_bugtask in non_malone_using_bugtasks
+ if related_bugtask.bugwatch is None]
+ self.assert_(
+ len(pending_bugwatch_bugtasks) > 0,
+ 'Bugtask %s on %s has no related bug watches elsewhere.' % (
+ bugtask.id, bugtask.target.displayname))
+
+ def assertBugTaskIsResolvedUpstream(self, bugtask):
+ """Make sure at least one of the related upstream tasks is resolved.
+
+ "Resolved", for our purposes, means either that one of the related
+ tasks is an upstream task in FIXCOMMITTED or FIXRELEASED state, or
+ it is a task with a bugwatch, and in FIXCOMMITTED, FIXRELEASED, or
+ INVALID state.
+ """
+ resolved_upstream_states = [
+ BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED]
+ resolved_bugwatch_states = [
+ BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED,
+ BugTaskStatus.INVALID]
+
+ # Helper functions for the list comprehension below.
+ def _is_resolved_upstream_task(bugtask):
+ return (
+ IUpstreamBugTask.providedBy(bugtask) and
+ bugtask.status in resolved_upstream_states)
+
+ def _is_resolved_bugwatch_task(bugtask):
+ return (
+ bugtask.bugwatch and bugtask.status in
+ resolved_bugwatch_states)
+
+ resolved_related_tasks = [
+ related_task for related_task in bugtask.related_tasks
+ if (_is_resolved_upstream_task(related_task) or
+ _is_resolved_bugwatch_task(related_task))]
+
+ self.assert_(len(resolved_related_tasks) > 0)
+ self.assert_(
+ len(resolved_related_tasks) > 0,
+ 'Bugtask %s on %s has no resolved related tasks.' % (
+ bugtask.id, bugtask.target.displayname))
+
+ def assertBugTaskIsOpenUpstream(self, bugtask):
+ """Make sure at least one of the related upstream tasks is open.
+
+ "Open", for our purposes, means either that one of the related
+ tasks is an upstream task or a task with a bugwatch which has
+ one of the states listed in open_states.
+ """
+ open_states = [
+ BugTaskStatus.NEW,
+ BugTaskStatus.INCOMPLETE,
+ BugTaskStatus.CONFIRMED,
+ BugTaskStatus.INPROGRESS,
+ BugTaskStatus.UNKNOWN]
+
+ # Helper functions for the list comprehension below.
+ def _is_open_upstream_task(bugtask):
+ return (
+ IUpstreamBugTask.providedBy(bugtask) and
+ bugtask.status in open_states)
+
+ def _is_open_bugwatch_task(bugtask):
+ return (
+ bugtask.bugwatch and bugtask.status in
+ open_states)
+
+ open_related_tasks = [
+ related_task for related_task in bugtask.related_tasks
+ if (_is_open_upstream_task(related_task) or
+ _is_open_bugwatch_task(related_task))]
+
+ self.assert_(
+ len(open_related_tasks) > 0,
+ 'Bugtask %s on %s has no open related tasks.' % (
+ bugtask.id, bugtask.target.displayname))
+
+ def _hasUpstreamTask(self, bug):
+ """Does this bug have an upstream task associated with it?
+
+ Returns True if yes, otherwise False.
+ """
+ for bugtask in bug.bugtasks:
+ if IUpstreamBugTask.providedBy(bugtask):
+ return True
+ return False
+
+ def assertShouldBeShownOnNoUpstreamTaskSearch(self, bugtask):
+ """Should the bugtask be shown in the search no upstream task search?
+
+ Returns True if yes, otherwise False.
+ """
+ self.assert_(
+ not self._hasUpstreamTask(bugtask.bug),
+ 'Bugtask %s on %s has upstream tasks.' % (
+ bugtask.id, bugtask.target.displayname))
+
+
+class BugTaskSetFindExpirableBugTasksTest(unittest.TestCase):
+ """Test `BugTaskSet.findExpirableBugTasks()` behaviour."""
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ """Setup the zope interaction and create expirable bugtasks."""
+ login('test@xxxxxxxxxxxxx')
+ self.user = getUtility(ILaunchBag).user
+ self.distribution = getUtility(IDistributionSet).getByName('ubuntu')
+ self.distroseries = self.distribution.getSeries('hoary')
+ self.product = getUtility(IProductSet).getByName('jokosher')
+ self.productseries = self.product.getSeries('trunk')
+ self.bugtaskset = getUtility(IBugTaskSet)
+ bugtasks = []
+ bugtasks.append(
+ create_old_bug("90 days old", 90, self.distribution))
+ bugtasks.append(
+ self.bugtaskset.createTask(
+ bug=bugtasks[-1].bug, owner=self.user,
+ distroseries=self.distroseries))
+ bugtasks.append(
+ create_old_bug("90 days old", 90, self.product))
+ bugtasks.append(
+ self.bugtaskset.createTask(
+ bug=bugtasks[-1].bug, owner=self.user,
+ productseries=self.productseries))
+ sync_bugtasks(bugtasks)
+
+ def tearDown(self):
+ logout()
+
+ def testSupportedTargetParam(self):
+ """The target param supports a limited set of BugTargets.
+
+ Four BugTarget types may passed as the target argument:
+ Distribution, DistroSeries, Product, ProductSeries.
+ """
+ supported_targets = [self.distribution, self.distroseries,
+ self.product, self.productseries]
+ for target in supported_targets:
+ expirable_bugtasks = self.bugtaskset.findExpirableBugTasks(
+ 0, self.user, target=target)
+ self.assertNotEqual(expirable_bugtasks.count(), 0,
+ "%s has %d expirable bugtasks." %
+ (self.distroseries, expirable_bugtasks.count()))
+
+ def testUnsupportedBugTargetParam(self):
+ """Test that unsupported targets raise errors.
+
+ Three BugTarget types are not supported because the UI does not
+ provide bug-index to link to the 'bugs that can expire' page.
+ ProjectGroup, SourcePackage, and DistributionSourcePackage will
+ raise an NotImplementedError.
+
+ Passing an unknown bugtarget type will raise an AssertionError.
+ """
+ project = getUtility(IProjectGroupSet).getByName('mozilla')
+ distributionsourcepackage = self.distribution.getSourcePackage(
+ 'mozilla-firefox')
+ sourcepackage = self.distroseries.getSourcePackage(
+ 'mozilla-firefox')
+ unsupported_targets = [project, distributionsourcepackage,
+ sourcepackage]
+ for target in unsupported_targets:
+ self.assertRaises(
+ NotImplementedError, self.bugtaskset.findExpirableBugTasks,
+ 0, self.user, target=target)
+
+ # Objects that are not a known BugTarget type raise an AssertionError.
+ self.assertRaises(
+ AssertionError, self.bugtaskset.findExpirableBugTasks,
+ 0, self.user, target=[])
+
+
+class BugTaskSetTest(unittest.TestCase):
+ """Test `BugTaskSet` methods."""
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ login(ANONYMOUS)
+
+ def test_getBugTasks(self):
+ """ IBugTaskSet.getBugTasks() returns a dictionary mapping the given
+ bugs to their bugtasks. It does that in a single query, to avoid
+ hitting the DB again when getting the bugs' tasks.
+ """
+ login('no-priv@xxxxxxxxxxxxx')
+ factory = LaunchpadObjectFactory()
+ bug1 = factory.makeBug()
+ factory.makeBugTask(bug1)
+ bug2 = factory.makeBug()
+ factory.makeBugTask(bug2)
+ factory.makeBugTask(bug2)
+
+ bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks(
+ [bug1.id, bug2.id])
+ # The bugtasks returned by getBugTasks() are exactly the same as the
+ # ones returned by bug.bugtasks, obviously.
+ self.failUnlessEqual(
+ set(bugs_and_tasks[bug1]).difference(bug1.bugtasks),
+ set([]))
+ self.failUnlessEqual(
+ set(bugs_and_tasks[bug2]).difference(bug2.bugtasks),
+ set([]))
+
+ def test_getBugTasks_with_empty_list(self):
+ # When given an empty list of bug IDs, getBugTasks() will return an
+ # empty dictionary.
+ bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks([])
+ self.failUnlessEqual(bugs_and_tasks, {})
+
+
+class TestBugTaskStatuses(TestCase):
+
+ def test_open_and_resolved_statuses(self):
+ """
+ There are constants that are used to define which statuses are for
+ resolved bugs (`RESOLVED_BUGTASK_STATUSES`), and which are for
+ unresolved bugs (`UNRESOLVED_BUGTASK_STATUSES`). The two constants
+ include all statuses defined in BugTaskStatus, except for Unknown.
+ """
+ self.assertNotIn(BugTaskStatus.UNKNOWN, RESOLVED_BUGTASK_STATUSES)
+ self.assertNotIn(BugTaskStatus.UNKNOWN, UNRESOLVED_BUGTASK_STATUSES)
+
+
+class TestGetStructuralSubscribers(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def make_product_with_bug(self):
+ product = self.factory.makeProduct()
+ bug = self.factory.makeBug(product=product)
+ # getStructuralSubscribers() is intended for use only in model code,
+ # and will not work if the bugtasks passed in are security proxied.
+ return product, removeSecurityProxy(bug)
+
+ def test_getStructuralSubscribers_no_subscribers(self):
+ # If there are no subscribers for any of the bug's targets then no
+ # subscribers will be returned by getStructuralSubscribers().
+ product, bug = self.make_product_with_bug()
+ bug_task_set = getUtility(IBugTaskSet)
+ subscribers = bug_task_set.getStructuralSubscribers(bug.bugtasks)
+ self.assertIsInstance(subscribers, ResultSet)
+ self.assertEqual([], list(subscribers))
+
+ def test_getStructuralSubscribers_single_target(self):
+ # Subscribers for any of the bug's targets are returned.
+ subscriber = self.factory.makePerson()
+ login_person(subscriber)
+ product, bug = self.make_product_with_bug()
+ product.addBugSubscription(subscriber, subscriber)
+ bug_task_set = getUtility(IBugTaskSet)
+ self.assertEqual(
+ [subscriber], list(
+ bug_task_set.getStructuralSubscribers(bug.bugtasks)))
+
+ def test_getStructuralSubscribers_multiple_targets(self):
+ # Subscribers for any of the bug's targets are returned.
+ actor = self.factory.makePerson()
+ login_person(actor)
+
+ subscriber1 = self.factory.makePerson()
+ subscriber2 = self.factory.makePerson()
+
+ product1 = self.factory.makeProduct(owner=actor)
+ product1.addBugSubscription(subscriber1, subscriber1)
+ product2 = self.factory.makeProduct(owner=actor)
+ product2.addBugSubscription(subscriber2, subscriber2)
+
+ bug = self.factory.makeBug(product=product1)
+ bug.addTask(actor, product2)
+
+ # getStructuralSubscribers() is intended for use only in model code,
+ # and will not work if the bugtasks passed in are security proxied.
+ bug = removeSecurityProxy(bug)
+
+ bug_task_set = getUtility(IBugTaskSet)
+ subscribers = bug_task_set.getStructuralSubscribers(bug.bugtasks)
+ self.assertIsInstance(subscribers, ResultSet)
+ self.assertEqual(set([subscriber1, subscriber2]), set(subscribers))
+
+ def test_getStructuralSubscribers_recipients(self):
+ # If provided, getStructuralSubscribers() calls the appropriate
+ # methods on a BugNotificationRecipients object.
+ subscriber = self.factory.makePerson()
+ login_person(subscriber)
+ product, bug = self.make_product_with_bug()
+ product.addBugSubscription(subscriber, subscriber)
+ recipients = BugNotificationRecipients()
+ bug_task_set = getUtility(IBugTaskSet)
+ subscribers = bug_task_set.getStructuralSubscribers(
+ bug.bugtasks, recipients=recipients)
+ # The return value is a list only when populating recipients.
+ self.assertIsInstance(subscribers, list)
+ self.assertEqual([subscriber], recipients.getRecipients())
+ reason, header = recipients.getReason(subscriber)
+ self.assertThat(
+ reason, StartsWith(
+ u"You received this bug notification because "
+ u"you are subscribed to "))
+ self.assertThat(header, StartsWith(u"Subscriber "))
+
+ def test_getStructuralSubscribers_level(self):
+ # getStructuralSubscribers() respects the given level.
+ subscriber = self.factory.makePerson()
+ login_person(subscriber)
+ product, bug = self.make_product_with_bug()
+ subscription = product.addBugSubscription(subscriber, subscriber)
+ subscription.bug_notification_level = BugNotificationLevel.METADATA
+ bug_task_set = getUtility(IBugTaskSet)
+ self.assertEqual(
+ [subscriber], list(
+ bug_task_set.getStructuralSubscribers(
+ bug.bugtasks, level=BugNotificationLevel.METADATA)))
+ subscription.bug_notification_level = BugNotificationLevel.METADATA
+ self.assertEqual(
+ [], list(
+ bug_task_set.getStructuralSubscribers(
+ bug.bugtasks, level=BugNotificationLevel.COMMENTS)))
+
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
suite.addTest(DocTestSuite('lp.bugs.model.bugtask'))
+ suite.addTest(
+ systemdocs.LayeredDocFileSuite(
+ 'test_bugtask_status.txt', layer=LaunchpadFunctionalLayer,
+ setUp=systemdocs.setUp, tearDown=systemdocs.tearDown))
return suite
=== renamed file 'lib/lp/bugs/tests/test_bugtask_status.txt' => 'lib/lp/bugs/model/tests/test_bugtask_status.txt'
=== modified file 'lib/lp/bugs/subscribers/bug.py'
--- lib/lp/bugs/subscribers/bug.py 2010-08-23 09:25:17 +0000
+++ lib/lp/bugs/subscribers/bug.py 2010-10-13 13:11:15 +0000
@@ -19,6 +19,8 @@
import datetime
from operator import attrgetter
+from zope.component import getUtility
+
from canonical.config import config
from canonical.database.sqlbase import block_implicit_flushes
from canonical.launchpad.helpers import get_contact_email_addresses
@@ -34,14 +36,12 @@
)
from lp.bugs.adapters.bugdelta import BugDelta
from lp.bugs.interfaces.bugchange import IBugChange
+from lp.bugs.interfaces.bugtask import IBugTaskSet
from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder
from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
from lp.bugs.mail.newbug import generate_bug_add_email
from lp.registry.enum import BugNotificationLevel
from lp.registry.interfaces.person import IPerson
-from lp.registry.interfaces.structuralsubscription import (
- IStructuralSubscriptionTarget,
- )
@block_implicit_flushes
@@ -179,15 +179,10 @@
if recipients is not None:
recipients.addAssignee(bugtask.assignee)
- if IStructuralSubscriptionTarget.providedBy(bugtask.target):
- also_notified_subscribers.update(
- bugtask.target.getBugNotificationsRecipients(
- recipients, level=level))
-
- if bugtask.milestone is not None:
- also_notified_subscribers.update(
- bugtask.milestone.getBugNotificationsRecipients(
- recipients, level=level))
+ # Get structural subscribers.
+ also_notified_subscribers.update(
+ getUtility(IBugTaskSet).getStructuralSubscribers(
+ [bugtask], recipients, level))
# If the target's bug supervisor isn't set,
# we add the owner as a subscriber.
=== removed file 'lib/lp/bugs/tests/test_bugtask_0.py'
--- lib/lp/bugs/tests/test_bugtask_0.py 2010-10-03 15:30:06 +0000
+++ lib/lp/bugs/tests/test_bugtask_0.py 1970-01-01 00:00:00 +0000
@@ -1,36 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Tests for bugtask.py."""
-
-__metaclass__ = type
-
-from doctest import (
- DocTestSuite,
- ELLIPSIS,
- NORMALIZE_WHITESPACE,
- REPORT_NDIFF,
- )
-
-
-def test_open_and_resolved_statuses(self):
- """
- There are constants that are used to define which statuses are for
- resolved bugs (RESOLVED_BUGTASK_STATUSES), and which are for
- unresolved bugs (UNRESOLVED_BUGTASK_STATUSES). The two constants
- include all statuses defined in BugTaskStatus, except for Unknown.
-
- >>> from canonical.launchpad.interfaces import (
- ... RESOLVED_BUGTASK_STATUSES, UNRESOLVED_BUGTASK_STATUSES)
- >>> from canonical.launchpad.interfaces import BugTaskStatus
- >>> not_included_status = set(BugTaskStatus.items).difference(
- ... RESOLVED_BUGTASK_STATUSES + UNRESOLVED_BUGTASK_STATUSES)
- >>> [status.name for status in not_included_status]
- ['UNKNOWN']
- """
-
-def test_suite():
- suite = DocTestSuite(
- optionflags=REPORT_NDIFF|NORMALIZE_WHITESPACE|ELLIPSIS)
- return suite
-
=== removed file 'lib/lp/bugs/tests/test_bugtask_1.py'
--- lib/lp/bugs/tests/test_bugtask_1.py 2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/tests/test_bugtask_1.py 1970-01-01 00:00:00 +0000
@@ -1,343 +0,0 @@
-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Bugtask related tests that are too complex to be readable as doctests."""
-
-__metaclass__ = type
-
-import unittest
-
-from zope.component import getUtility
-
-from canonical.database.sqlbase import flush_database_updates
-from canonical.launchpad.ftests import (
- ANONYMOUS,
- login,
- logout,
- )
-from canonical.launchpad.webapp.interfaces import ILaunchBag
-from canonical.testing.layers import DatabaseFunctionalLayer
-from lp.app.enums import ServiceUsage
-from lp.bugs.interfaces.bug import IBugSet
-from lp.bugs.interfaces.bugtask import (
- BugTaskStatus,
- IBugTaskSet,
- IUpstreamBugTask,
- )
-from lp.bugs.interfaces.bugwatch import IBugWatchSet
-from lp.bugs.tests.bug import (
- create_old_bug,
- sync_bugtasks,
- )
-from lp.registry.interfaces.distribution import IDistributionSet
-from lp.registry.interfaces.product import IProductSet
-from lp.registry.interfaces.projectgroup import IProjectGroupSet
-from lp.testing.factory import LaunchpadObjectFactory
-
-
-class BugTaskSearchBugsElsewhereTest(unittest.TestCase):
- """Tests for searching bugs filtering on related bug tasks.
-
- It also acts as a helper class, which makes related doctests more
- readable, since they can use methods from this class.
- """
- layer = DatabaseFunctionalLayer
-
- def __init__(self, methodName='runTest', helper_only=False):
- """If helper_only is True, set up it only as a helper class."""
- if not helper_only:
- unittest.TestCase.__init__(self, methodName=methodName)
-
- def setUp(self):
- login(ANONYMOUS)
-
- def tearDown(self):
- logout()
-
- def _getBugTaskByTarget(self, bug, target):
- """Return a bug's bugtask for the given target."""
- for bugtask in bug.bugtasks:
- if bugtask.target == target:
- return bugtask
- else:
- raise AssertionError(
- "Didn't find a %s task on bug %s." % (
- target.bugtargetname, bug.id))
-
- def setUpBugsResolvedUpstreamTests(self):
- """Modify some bugtasks to match the resolved upstream filter."""
- bugset = getUtility(IBugSet)
- productset = getUtility(IProductSet)
- firefox = productset.getByName("firefox")
- thunderbird = productset.getByName("thunderbird")
-
- # Mark an upstream task on bug #1 "Fix Released"
- bug_one = bugset.get(1)
- firefox_upstream = self._getBugTaskByTarget(bug_one, firefox)
- self.assertEqual(
- ServiceUsage.LAUNCHPAD,
- firefox_upstream.product.bug_tracking_usage)
- self.old_firefox_status = firefox_upstream.status
- firefox_upstream.transitionToStatus(
- BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
- self.firefox_upstream = firefox_upstream
-
- # Mark an upstream task on bug #9 "Fix Committed"
- bug_nine = bugset.get(9)
- thunderbird_upstream = self._getBugTaskByTarget(bug_nine, thunderbird)
- self.old_thunderbird_status = thunderbird_upstream.status
- thunderbird_upstream.transitionToStatus(
- BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user)
- self.thunderbird_upstream = thunderbird_upstream
-
- # Add a watch to a Debian bug for bug #2, and mark the task Fix
- # Released.
- bug_two = bugset.get(2)
- current_user = getUtility(ILaunchBag).user
- bugtaskset = getUtility(IBugTaskSet)
- bugwatchset = getUtility(IBugWatchSet)
-
- # Get a debbugs watch.
- watch_debbugs_327452 = bugwatchset.get(9)
- self.assertEquals(watch_debbugs_327452.bugtracker.name, "debbugs")
- self.assertEquals(watch_debbugs_327452.remotebug, "327452")
-
- # Associate the watch to a Fix Released task.
- debian = getUtility(IDistributionSet).getByName("debian")
- debian_firefox = debian.getSourcePackage("mozilla-firefox")
- bug_two_in_debian_firefox = self._getBugTaskByTarget(
- bug_two, debian_firefox)
- bug_two_in_debian_firefox.bugwatch = watch_debbugs_327452
- bug_two_in_debian_firefox.transitionToStatus(
- BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user)
-
- flush_database_updates()
-
- def tearDownBugsElsewhereTests(self):
- """Resets the modified bugtasks to their original statuses."""
- self.firefox_upstream.transitionToStatus(
- self.old_firefox_status, getUtility(ILaunchBag).user)
- self.thunderbird_upstream.transitionToStatus(
- self.old_thunderbird_status, getUtility(ILaunchBag).user)
- flush_database_updates()
-
- def assertBugTaskIsPendingBugWatchElsewhere(self, bugtask):
- """Assert the bugtask is pending a bug watch elsewhere.
-
- Pending a bugwatch elsewhere means that at least one of the bugtask's
- related task's target isn't using Malone, and that
- related_bugtask.bugwatch is None.
- """
- non_malone_using_bugtasks = [
- related_task for related_task in bugtask.related_tasks
- if not related_task.target_uses_malone]
- pending_bugwatch_bugtasks = [
- related_bugtask for related_bugtask in non_malone_using_bugtasks
- if related_bugtask.bugwatch is None]
- self.assert_(
- len(pending_bugwatch_bugtasks) > 0,
- 'Bugtask %s on %s has no related bug watches elsewhere.' % (
- bugtask.id, bugtask.target.displayname))
-
- def assertBugTaskIsResolvedUpstream(self, bugtask):
- """Make sure at least one of the related upstream tasks is resolved.
-
- "Resolved", for our purposes, means either that one of the related
- tasks is an upstream task in FIXCOMMITTED or FIXRELEASED state, or
- it is a task with a bugwatch, and in FIXCOMMITTED, FIXRELEASED, or
- INVALID state.
- """
- resolved_upstream_states = [
- BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED]
- resolved_bugwatch_states = [
- BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED,
- BugTaskStatus.INVALID]
-
- # Helper functions for the list comprehension below.
- def _is_resolved_upstream_task(bugtask):
- return (
- IUpstreamBugTask.providedBy(bugtask) and
- bugtask.status in resolved_upstream_states)
-
- def _is_resolved_bugwatch_task(bugtask):
- return (
- bugtask.bugwatch and bugtask.status in
- resolved_bugwatch_states)
-
- resolved_related_tasks = [
- related_task for related_task in bugtask.related_tasks
- if (_is_resolved_upstream_task(related_task) or
- _is_resolved_bugwatch_task(related_task))]
-
- self.assert_(len(resolved_related_tasks) > 0)
- self.assert_(
- len(resolved_related_tasks) > 0,
- 'Bugtask %s on %s has no resolved related tasks.' % (
- bugtask.id, bugtask.target.displayname))
-
- def assertBugTaskIsOpenUpstream(self, bugtask):
- """Make sure at least one of the related upstream tasks is open.
-
- "Open", for our purposes, means either that one of the related
- tasks is an upstream task or a task with a bugwatch which has
- one of the states listed in open_states.
- """
- open_states = [
- BugTaskStatus.NEW,
- BugTaskStatus.INCOMPLETE,
- BugTaskStatus.CONFIRMED,
- BugTaskStatus.INPROGRESS,
- BugTaskStatus.UNKNOWN]
-
- # Helper functions for the list comprehension below.
- def _is_open_upstream_task(bugtask):
- return (
- IUpstreamBugTask.providedBy(bugtask) and
- bugtask.status in open_states)
-
- def _is_open_bugwatch_task(bugtask):
- return (
- bugtask.bugwatch and bugtask.status in
- open_states)
-
- open_related_tasks = [
- related_task for related_task in bugtask.related_tasks
- if (_is_open_upstream_task(related_task) or
- _is_open_bugwatch_task(related_task))]
-
- self.assert_(
- len(open_related_tasks) > 0,
- 'Bugtask %s on %s has no open related tasks.' % (
- bugtask.id, bugtask.target.displayname))
-
- def _hasUpstreamTask(self, bug):
- """Does this bug have an upstream task associated with it?
-
- Returns True if yes, otherwise False.
- """
- for bugtask in bug.bugtasks:
- if IUpstreamBugTask.providedBy(bugtask):
- return True
- return False
-
- def assertShouldBeShownOnNoUpstreamTaskSearch(self, bugtask):
- """Should the bugtask be shown in the search no upstream task search?
-
- Returns True if yes, otherwise False.
- """
- self.assert_(
- not self._hasUpstreamTask(bugtask.bug),
- 'Bugtask %s on %s has upstream tasks.' % (
- bugtask.id, bugtask.target.displayname))
-
-
-class BugTaskSetFindExpirableBugTasksTest(unittest.TestCase):
- """Test `BugTaskSet.findExpirableBugTasks()` behaviour."""
- layer = DatabaseFunctionalLayer
-
- def setUp(self):
- """Setup the zope interaction and create expirable bugtasks."""
- login('test@xxxxxxxxxxxxx')
- self.user = getUtility(ILaunchBag).user
- self.distribution = getUtility(IDistributionSet).getByName('ubuntu')
- self.distroseries = self.distribution.getSeries('hoary')
- self.product = getUtility(IProductSet).getByName('jokosher')
- self.productseries = self.product.getSeries('trunk')
- self.bugtaskset = getUtility(IBugTaskSet)
- bugtasks = []
- bugtasks.append(
- create_old_bug("90 days old", 90, self.distribution))
- bugtasks.append(
- self.bugtaskset.createTask(
- bug=bugtasks[-1].bug, owner=self.user,
- distroseries=self.distroseries))
- bugtasks.append(
- create_old_bug("90 days old", 90, self.product))
- bugtasks.append(
- self.bugtaskset.createTask(
- bug=bugtasks[-1].bug, owner=self.user,
- productseries=self.productseries))
- sync_bugtasks(bugtasks)
-
- def tearDown(self):
- logout()
-
- def testSupportedTargetParam(self):
- """The target param supports a limited set of BugTargets.
-
- Four BugTarget types may passed as the target argument:
- Distribution, DistroSeries, Product, ProductSeries.
- """
- supported_targets = [self.distribution, self.distroseries,
- self.product, self.productseries]
- for target in supported_targets:
- expirable_bugtasks = self.bugtaskset.findExpirableBugTasks(
- 0, self.user, target=target)
- self.assertNotEqual(expirable_bugtasks.count(), 0,
- "%s has %d expirable bugtasks." %
- (self.distroseries, expirable_bugtasks.count()))
-
- def testUnsupportedBugTargetParam(self):
- """Test that unsupported targets raise errors.
-
- Three BugTarget types are not supported because the UI does not
- provide bug-index to link to the 'bugs that can expire' page.
- ProjectGroup, SourcePackage, and DistributionSourcePackage will
- raise an NotImplementedError.
-
- Passing an unknown bugtarget type will raise an AssertionError.
- """
- project = getUtility(IProjectGroupSet).getByName('mozilla')
- distributionsourcepackage = self.distribution.getSourcePackage(
- 'mozilla-firefox')
- sourcepackage = self.distroseries.getSourcePackage(
- 'mozilla-firefox')
- unsupported_targets = [project, distributionsourcepackage,
- sourcepackage]
- for target in unsupported_targets:
- self.assertRaises(
- NotImplementedError, self.bugtaskset.findExpirableBugTasks,
- 0, self.user, target=target)
-
- # Objects that are not a known BugTarget type raise an AssertionError.
- self.assertRaises(
- AssertionError, self.bugtaskset.findExpirableBugTasks,
- 0, self.user, target=[])
-
-
-class BugTaskSetTest(unittest.TestCase):
- """Test `BugTaskSet` methods."""
- layer = DatabaseFunctionalLayer
-
- def setUp(self):
- login(ANONYMOUS)
-
- def test_getBugTasks(self):
- """ IBugTaskSet.getBugTasks() returns a dictionary mapping the given
- bugs to their bugtasks. It does that in a single query, to avoid
- hitting the DB again when getting the bugs' tasks.
- """
- login('no-priv@xxxxxxxxxxxxx')
- factory = LaunchpadObjectFactory()
- bug1 = factory.makeBug()
- factory.makeBugTask(bug1)
- bug2 = factory.makeBug()
- factory.makeBugTask(bug2)
- factory.makeBugTask(bug2)
-
- bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks(
- [bug1.id, bug2.id])
- # The bugtasks returned by getBugTasks() are exactly the same as the
- # ones returned by bug.bugtasks, obviously.
- self.failUnlessEqual(
- set(bugs_and_tasks[bug1]).difference(bug1.bugtasks),
- set([]))
- self.failUnlessEqual(
- set(bugs_and_tasks[bug2]).difference(bug2.bugtasks),
- set([]))
-
- def test_getBugTasks_with_empty_list(self):
- # When given an empty list of bug IDs, getBugTasks() will return an
- # empty dictionary.
- bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks([])
- self.failUnlessEqual(bugs_and_tasks, {})
=== removed file 'lib/lp/bugs/tests/test_bugtask_status.py'
--- lib/lp/bugs/tests/test_bugtask_status.py 2010-10-04 19:50:45 +0000
+++ lib/lp/bugs/tests/test_bugtask_status.py 1970-01-01 00:00:00 +0000
@@ -1,22 +0,0 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""Test for choosing the request and publication."""
-
-__metaclass__ = type
-
-from canonical.launchpad.testing.systemdocs import (
- LayeredDocFileSuite,
- setUp,
- tearDown,
- )
-from canonical.testing.layers import LaunchpadFunctionalLayer
-
-
-def test_suite():
- suite = LayeredDocFileSuite(
- 'test_bugtask_status.txt',
- layer=LaunchpadFunctionalLayer, setUp=setUp, tearDown=tearDown,
- )
- return suite
-
=== modified file 'lib/lp/registry/doc/structural-subscriptions.txt'
--- lib/lp/registry/doc/structural-subscriptions.txt 2010-10-13 13:11:13 +0000
+++ lib/lp/registry/doc/structural-subscriptions.txt 2010-10-13 13:11:15 +0000
@@ -85,12 +85,7 @@
When notifying subscribers of bug activity, both subscribers to the
target and to the target's parent are notified.
- >>> from canonical.launchpad.ftests import syncUpdate
>>> from lp.registry.enum import BugNotificationLevel
- >>> from canonical.launchpad.interfaces import (
- ... BlueprintNotificationLevel)
- >>> from lp.bugs.mail.bugnotificationrecipients import (
- ... BugNotificationRecipients)
We define some utility functions for printing out bug subscriptions and
the recipients for the notifications they generate.
@@ -114,89 +109,6 @@
>>> ubuntu_sub.bug_notification_level = BugNotificationLevel.COMMENTS
>>> evolution_sub.bug_notification_level = BugNotificationLevel.COMMENTS
-`getBugNotificationsRecipients` returns all the bug subscribers to the
-target and its parent, and adds the rationale for the subscriptions to
-the recipients set. Each subscriber is only added once.
-
- >>> recipients = BugNotificationRecipients()
- >>> bug_subscribers = evolution_package.getBugNotificationsRecipients(
- ... recipients=recipients)
- >>> print_bug_subscriptions(ubuntu.bug_subscriptions)
- name12
- >>> print_bug_subscriptions(evolution_package.bug_subscriptions)
- name12
- >>> print_bug_subscribers(bug_subscribers)
- name12
- >>> print_bug_recipients(recipients)
- name12 "Subscriber (evolution in ubuntu)"
-
-Foo Bar subscribes to Ubuntu.
-
- >>> login('foo.bar@xxxxxxxxxxxxx')
- >>> foobar_subscription = ubuntu.addBugSubscription(foobar, foobar)
- >>> recipients = BugNotificationRecipients()
-
-The set of subscribers to the evolution package for ubuntu now includes
-both subscribers to the package, and subscribers to the distribution.
-
- >>> bug_subscribers = evolution_package.getBugNotificationsRecipients(
- ... recipients=recipients)
- >>> print_bug_recipients(recipients)
- name16 "Subscriber (Ubuntu)"
- name12 "Subscriber (evolution in ubuntu)"
-
-We can pass the parameter `level` to getBugNotificationsRecipients().
-Subscribers whose subscription level is lower than the given parameter
-are not returned.
-
- >>> foobar_subscription.bug_notification_level = (
- ... BugNotificationLevel.METADATA)
- >>> recipients = BugNotificationRecipients()
- >>> bug_subscribers = evolution_package.getBugNotificationsRecipients(
- ... recipients=recipients, level=BugNotificationLevel.COMMENTS)
- >>> print_bug_recipients(recipients)
- name12 "Subscriber (evolution in ubuntu)"
-
-We remove Sample Person's bug subscription to the package.
-
- >>> evolution_sub.blueprint_notification_level = (
- ... BlueprintNotificationLevel.METADATA)
- >>> evolution_package.removeBugSubscription(sampleperson, sampleperson)
- >>> ubuntu.removeBugSubscription(sampleperson, sampleperson)
- >>> syncUpdate(evolution_sub)
-
-Sample Person is no longer a subscriber to the package, but Foo Bar
-is still a subscriber, by being subscribed to Ubuntu.
-
- >>> print_bug_subscribers(
- ... evolution_package.getBugNotificationsRecipients(
- ... recipients=recipients))
- name16
-
-A project is the parent of each of its products.
-
-Fireox does not have any subscribers.
-
- >>> print_bug_subscribers(firefox.getBugNotificationsRecipients())
-
-Mozilla is the parent of Fireox.
-
- >>> from canonical.launchpad.interfaces import IProjectGroupSet
- >>> mozilla = getUtility(IProjectGroupSet).getByName('mozilla')
- >>> print firefox.parent_subscription_target.displayname
- the Mozilla Project
-
-Foobar subscribes to bug notificatios for Mozilla.
-
- >>> mozilla.addBugSubscription(foobar, foobar)
- <StructuralSubscription at ...>
-
-As a result of subscribing to Mozilla, Foobar is now a subscriber of
-Firefox.
-
- >>> print_bug_subscribers(firefox.getBugNotificationsRecipients())
- name16
-
Target type display
===================
=== modified file 'lib/lp/registry/interfaces/structuralsubscription.py'
--- lib/lp/registry/interfaces/structuralsubscription.py 2010-10-13 13:11:13 +0000
+++ lib/lp/registry/interfaces/structuralsubscription.py 2010-10-13 13:11:15 +0000
@@ -181,20 +181,6 @@
def getSubscription(person):
"""Return the subscription for `person`, if it exists."""
- def getBugNotificationsRecipients(recipients=None, level=None):
- """Return the set of bug subscribers to this target.
-
- :param recipients: If recipients is not None, a rationale
- is added for each subscriber.
- :type recipients: `INotificationRecipientSet`
- 'param level: If level is not None, only strucutral
- subscribers with a subscrition level greater or equal
- to the given value are returned.
- :type level: `BugNotificationLevel`
- :return: An `INotificationRecipientSet` instance containing
- the bug subscribers.
- """
-
target_type_display = Attribute("The type of the target, for display.")
def userHasBugSubscriptions(user):
=== modified file 'lib/lp/registry/model/structuralsubscription.py'
--- lib/lp/registry/model/structuralsubscription.py 2010-10-13 13:11:13 +0000
+++ lib/lp/registry/model/structuralsubscription.py 2010-10-13 13:11:15 +0000
@@ -448,24 +448,6 @@
return StructuralSubscription.select(
query, orderBy='Person.displayname', clauseTables=['Person'])
- def getBugNotificationsRecipients(self, recipients=None, level=None):
- """See `IStructuralSubscriptionTarget`."""
- if level is None:
- subscriptions = self.bug_subscriptions
- else:
- subscriptions = self.getSubscriptions(
- min_bug_notification_level=level)
- subscribers = set(
- subscription.subscriber for subscription in subscriptions)
- if recipients is not None:
- for subscriber in subscribers:
- recipients.addStructuralSubscriber(subscriber, self)
- parent = self.parent_subscription_target
- if parent is not None:
- subscribers.update(
- parent.getBugNotificationsRecipients(recipients, level))
- return subscribers
-
@property
def bug_subscriptions(self):
"""See `IStructuralSubscriptionTarget`."""
Follow ups