Merge lp:~allenap/launchpad/revert-subscribe-to-tag-bug-151129 into lp:launchpad
- revert-subscribe-to-tag-bug-151129
- Merge into devel
Proposed by
Gavin Panella
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Gavin Panella | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 11985 | ||||
Proposed branch: | lp:~allenap/launchpad/revert-subscribe-to-tag-bug-151129 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
888 lines (+176/-500) 2 files modified
lib/lp/registry/model/structuralsubscription.py (+47/-252) lib/lp/registry/tests/test_structuralsubscriptiontarget.py (+129/-248) |
||||
To merge this branch: | bzr merge lp:~allenap/launchpad/revert-subscribe-to-tag-bug-151129 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francis J. Lacoste (community) | Approve | ||
Gavin Panella (community) | Approve | ||
Review via email: mp+41901@code.launchpad.net |
Commit message
[r=allenap]
Description of the change
Subscribing to tags has performance problems. See the linked bug for more information. This branch reverts the change.
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/registry/model/structuralsubscription.py' |
2 | --- lib/lp/registry/model/structuralsubscription.py 2010-11-19 14:33:01 +0000 |
3 | +++ lib/lp/registry/model/structuralsubscription.py 2010-11-25 21:03:11 +0000 |
4 | @@ -2,26 +2,14 @@ |
5 | # GNU Affero General Public License version 3 (see the file LICENSE). |
6 | |
7 | __metaclass__ = type |
8 | -__all__ = [ |
9 | - 'StructuralSubscription', |
10 | - 'StructuralSubscriptionTargetMixin', |
11 | - ] |
12 | +__all__ = ['StructuralSubscription', |
13 | + 'StructuralSubscriptionTargetMixin'] |
14 | |
15 | from sqlobject import ForeignKey |
16 | from storm.expr import ( |
17 | - Alias, |
18 | And, |
19 | - CompoundOper, |
20 | - Except, |
21 | - In, |
22 | - Intersect, |
23 | LeftJoin, |
24 | - NamedFunc, |
25 | - Not, |
26 | Or, |
27 | - Select, |
28 | - SQL, |
29 | - Union, |
30 | ) |
31 | from storm.store import Store |
32 | from zope.component import ( |
33 | @@ -46,7 +34,6 @@ |
34 | from lp.bugs.model.bugsubscriptionfilterstatus import ( |
35 | BugSubscriptionFilterStatus, |
36 | ) |
37 | -from lp.bugs.model.bugsubscriptionfiltertag import BugSubscriptionFilterTag |
38 | from lp.registry.enum import BugNotificationLevel |
39 | from lp.registry.errors import ( |
40 | DeleteSubscriptionError, |
41 | @@ -484,242 +471,50 @@ |
42 | |
43 | def getSubscriptionsForBugTask(self, bugtask, level): |
44 | """See `IStructuralSubscriptionTarget`.""" |
45 | - set_builder = BugFilterSetBuilder( |
46 | - bugtask, level, self.__helper.join) |
47 | - return Store.of(self.__helper.pillar).find( |
48 | - StructuralSubscription, In( |
49 | - StructuralSubscription.id, |
50 | - set_builder.subscriptions)) |
51 | - |
52 | - |
53 | -class ArrayAgg(NamedFunc): |
54 | - __slots__ = () |
55 | - name = "ARRAY_AGG" |
56 | - |
57 | - |
58 | -class ArrayContains(CompoundOper): |
59 | - __slots__ = () |
60 | - oper = "@>" |
61 | - |
62 | - |
63 | -class BugFilterSetBuilder: |
64 | - """A convenience class to build queries for getSubscriptionsForBugTask.""" |
65 | - |
66 | - def __init__(self, bugtask, level, join_condition): |
67 | - """Initialize a new set builder for bug filters. |
68 | - |
69 | - :param bugtask: The `IBugTask` to match against. |
70 | - :param level: A member of `BugNotificationLevel`. |
71 | - :param join_condition: A condition for selecting structural |
72 | - subscriptions. Generally this should limit the subscriptions to a |
73 | - particular target (i.e. project or distribution). |
74 | - """ |
75 | - self.status = bugtask.status |
76 | - self.importance = bugtask.importance |
77 | - # The list() gets around some weirdness with security proxies; Storm |
78 | - # does not know how to compile an expression with a proxied list. |
79 | - self.tags = list(bugtask.bug.tags) |
80 | - # Set up common conditions. |
81 | - self.base_conditions = And( |
82 | + origin = [ |
83 | + StructuralSubscription, |
84 | + LeftJoin( |
85 | + BugSubscriptionFilter, |
86 | + BugSubscriptionFilter.structural_subscription_id == ( |
87 | + StructuralSubscription.id)), |
88 | + LeftJoin( |
89 | + BugSubscriptionFilterStatus, |
90 | + BugSubscriptionFilterStatus.filter_id == ( |
91 | + BugSubscriptionFilter.id)), |
92 | + LeftJoin( |
93 | + BugSubscriptionFilterImportance, |
94 | + BugSubscriptionFilterImportance.filter_id == ( |
95 | + BugSubscriptionFilter.id)), |
96 | + ] |
97 | + |
98 | + if len(bugtask.bug.tags) == 0: |
99 | + tag_conditions = [ |
100 | + BugSubscriptionFilter.include_any_tags == False, |
101 | + ] |
102 | + else: |
103 | + tag_conditions = [ |
104 | + BugSubscriptionFilter.exclude_any_tags == False, |
105 | + ] |
106 | + |
107 | + conditions = [ |
108 | StructuralSubscription.bug_notification_level >= level, |
109 | - join_condition) |
110 | - # Set up common filter conditions. |
111 | - if len(self.tags) == 0: |
112 | - self.filter_conditions = And( |
113 | - # When the bug has no tags, filters with include_any_tags set |
114 | - # can never match. |
115 | - Not(BugSubscriptionFilter.include_any_tags), |
116 | - self.base_conditions) |
117 | - else: |
118 | - self.filter_conditions = And( |
119 | - # When the bug has tags, filters with exclude_any_tags set can |
120 | - # never match. |
121 | - Not(BugSubscriptionFilter.exclude_any_tags), |
122 | - self.base_conditions) |
123 | - |
124 | - @property |
125 | - def subscriptions_without_filters(self): |
126 | - """Subscriptions without filters.""" |
127 | - return Select( |
128 | - StructuralSubscription.id, |
129 | - tables=( |
130 | - StructuralSubscription, |
131 | - LeftJoin( |
132 | - BugSubscriptionFilter, |
133 | - BugSubscriptionFilter.structural_subscription_id == ( |
134 | - StructuralSubscription.id))), |
135 | - where=And( |
136 | + Or( |
137 | + # There's no filter or ... |
138 | BugSubscriptionFilter.id == None, |
139 | - self.base_conditions)) |
140 | - |
141 | - def _filters_matching_x(self, join, where_condition, **extra): |
142 | - """Return an expression yielding `(subscription_id, filter_id)` rows. |
143 | - |
144 | - The expressions returned by this function are used in set (union, |
145 | - intersect, except) operations at the *filter* level. However, the |
146 | - interesting result of these set operations is the structural |
147 | - subscription, hence both columns are included in the expressions |
148 | - generated. Since a structural subscription can have zero or more |
149 | - filters, and a filter can never be associated with more than one |
150 | - subscription, the set operations are unaffected. |
151 | - """ |
152 | - return Select( |
153 | - columns=( |
154 | - # Alias this column so it can be selected in |
155 | - # subscriptions_matching. |
156 | - Alias( |
157 | - BugSubscriptionFilter.structural_subscription_id, |
158 | - "structural_subscription_id"), |
159 | - BugSubscriptionFilter.id), |
160 | - tables=( |
161 | - StructuralSubscription, BugSubscriptionFilter, join), |
162 | - where=And( |
163 | - BugSubscriptionFilter.structural_subscription_id == ( |
164 | - StructuralSubscription.id), |
165 | - self.filter_conditions, |
166 | - where_condition), |
167 | - **extra) |
168 | - |
169 | - @property |
170 | - def filters_matching_status(self): |
171 | - """Filters with the given bugtask's status.""" |
172 | - join = LeftJoin( |
173 | - BugSubscriptionFilterStatus, |
174 | - BugSubscriptionFilterStatus.filter_id == ( |
175 | - BugSubscriptionFilter.id)) |
176 | - condition = Or( |
177 | - BugSubscriptionFilterStatus.id == None, |
178 | - BugSubscriptionFilterStatus.status == self.status) |
179 | - return self._filters_matching_x(join, condition) |
180 | - |
181 | - @property |
182 | - def filters_matching_importance(self): |
183 | - """Filters with the given bugtask's importance.""" |
184 | - join = LeftJoin( |
185 | - BugSubscriptionFilterImportance, |
186 | - BugSubscriptionFilterImportance.filter_id == ( |
187 | - BugSubscriptionFilter.id)) |
188 | - condition = Or( |
189 | - BugSubscriptionFilterImportance.id == None, |
190 | - BugSubscriptionFilterImportance.importance == self.importance) |
191 | - return self._filters_matching_x(join, condition) |
192 | - |
193 | - @property |
194 | - def filters_without_include_tags(self): |
195 | - """Filters with no tags required.""" |
196 | - join = LeftJoin( |
197 | - BugSubscriptionFilterTag, |
198 | - And(BugSubscriptionFilterTag.filter_id == ( |
199 | - BugSubscriptionFilter.id), |
200 | - BugSubscriptionFilterTag.include)) |
201 | - return self._filters_matching_x( |
202 | - join, BugSubscriptionFilterTag.id == None) |
203 | - |
204 | - @property |
205 | - def filters_matching_any_include_tags(self): |
206 | - """Filters including any of the bug's tags.""" |
207 | - condition = And( |
208 | - BugSubscriptionFilterTag.filter_id == ( |
209 | - BugSubscriptionFilter.id), |
210 | - BugSubscriptionFilterTag.include, |
211 | - Not(BugSubscriptionFilter.find_all_tags), |
212 | - In(BugSubscriptionFilterTag.tag, self.tags)) |
213 | - return self._filters_matching_x( |
214 | - BugSubscriptionFilterTag, condition) |
215 | - |
216 | - @property |
217 | - def filters_matching_any_exclude_tags(self): |
218 | - """Filters excluding any of the bug's tags.""" |
219 | - condition = And( |
220 | - BugSubscriptionFilterTag.filter_id == ( |
221 | - BugSubscriptionFilter.id), |
222 | - Not(BugSubscriptionFilterTag.include), |
223 | - Not(BugSubscriptionFilter.find_all_tags), |
224 | - In(BugSubscriptionFilterTag.tag, self.tags)) |
225 | - return self._filters_matching_x( |
226 | - BugSubscriptionFilterTag, condition) |
227 | - |
228 | - def _filters_matching_all_x_tags(self, where_condition): |
229 | - """Return an expression yielding `(subscription_id, filter_id)` rows. |
230 | - |
231 | - This joins to `BugSubscriptionFilterTag` and calls up to |
232 | - `_filters_matching_x`, and groups by filter. Conditions are added to |
233 | - ensure that all rows in each group are a subset of the bug's tags. |
234 | - """ |
235 | - tags_array = "ARRAY[%s]::TEXT[]" % ",".join( |
236 | - quote(tag) for tag in self.tags) |
237 | - return self._filters_matching_x( |
238 | - BugSubscriptionFilterTag, |
239 | - And( |
240 | - BugSubscriptionFilterTag.filter_id == ( |
241 | - BugSubscriptionFilter.id), |
242 | - BugSubscriptionFilter.find_all_tags, |
243 | - self.filter_conditions, |
244 | - where_condition), |
245 | - group_by=( |
246 | - BugSubscriptionFilter.structural_subscription_id, |
247 | - BugSubscriptionFilter.id), |
248 | - having=ArrayContains( |
249 | - SQL(tags_array), ArrayAgg( |
250 | - BugSubscriptionFilterTag.tag))) |
251 | - |
252 | - @property |
253 | - def filters_matching_all_include_tags(self): |
254 | - """Filters including the bug's tags.""" |
255 | - return self._filters_matching_all_x_tags( |
256 | - BugSubscriptionFilterTag.include) |
257 | - |
258 | - @property |
259 | - def filters_matching_all_exclude_tags(self): |
260 | - """Filters excluding the bug's tags.""" |
261 | - return self._filters_matching_all_x_tags( |
262 | - Not(BugSubscriptionFilterTag.include)) |
263 | - |
264 | - @property |
265 | - def filters_matching_include_tags(self): |
266 | - """Filters with tag filters including the bug.""" |
267 | - return Union( |
268 | - self.filters_matching_any_include_tags, |
269 | - self.filters_matching_all_include_tags) |
270 | - |
271 | - @property |
272 | - def filters_matching_exclude_tags(self): |
273 | - """Filters with tag filters excluding the bug.""" |
274 | - return Union( |
275 | - self.filters_matching_any_exclude_tags, |
276 | - self.filters_matching_all_exclude_tags) |
277 | - |
278 | - @property |
279 | - def filters_matching_tags(self): |
280 | - """Filters with tag filters matching the bug.""" |
281 | - if len(self.tags) == 0: |
282 | - # The filter's required tags must be an empty set. The filter's |
283 | - # excluded tags can be anything so no condition is needed. |
284 | - return self.filters_without_include_tags |
285 | - else: |
286 | - return Except( |
287 | - Union(self.filters_without_include_tags, |
288 | - self.filters_matching_include_tags), |
289 | - self.filters_matching_exclude_tags) |
290 | - |
291 | - @property |
292 | - def filters_matching(self): |
293 | - """Filters matching the bug.""" |
294 | - return Intersect( |
295 | - self.filters_matching_status, |
296 | - self.filters_matching_importance, |
297 | - self.filters_matching_tags) |
298 | - |
299 | - @property |
300 | - def subscriptions_with_matching_filters(self): |
301 | - """Subscriptions with one or more filters matching the bug.""" |
302 | - return Select( |
303 | - # I don't know of a more Storm-like way of doing this. |
304 | - SQL("filters_matching.structural_subscription_id"), |
305 | - tables=Alias(self.filters_matching, "filters_matching")) |
306 | - |
307 | - @property |
308 | - def subscriptions(self): |
309 | - return Union( |
310 | - self.subscriptions_without_filters, |
311 | - self.subscriptions_with_matching_filters) |
312 | + # There is a filter and ... |
313 | + And( |
314 | + # There's no status filter, or there is a status filter |
315 | + # and and it matches. |
316 | + Or(BugSubscriptionFilterStatus.id == None, |
317 | + BugSubscriptionFilterStatus.status == bugtask.status), |
318 | + # There's no importance filter, or there is an importance |
319 | + # filter and it matches. |
320 | + Or(BugSubscriptionFilterImportance.id == None, |
321 | + BugSubscriptionFilterImportance.importance == ( |
322 | + bugtask.importance)), |
323 | + # Any number of conditions relating to tags. |
324 | + *tag_conditions)), |
325 | + ] |
326 | + |
327 | + return Store.of(self.__helper.pillar).using(*origin).find( |
328 | + StructuralSubscription, self.__helper.join, *conditions) |
329 | |
330 | === modified file 'lib/lp/registry/tests/test_structuralsubscriptiontarget.py' |
331 | --- lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2010-11-18 21:29:44 +0000 |
332 | +++ lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2010-11-25 21:03:11 +0000 |
333 | @@ -32,6 +32,7 @@ |
334 | BugTaskImportance, |
335 | BugTaskStatus, |
336 | ) |
337 | +from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter |
338 | from lp.bugs.tests.test_bugtarget import bugtarget_filebug |
339 | from lp.registry.enum import BugNotificationLevel |
340 | from lp.registry.errors import ( |
341 | @@ -56,11 +57,10 @@ |
342 | from lp.testing.matchers import Provides |
343 | |
344 | |
345 | -class RestrictedStructuralSubscriptionTestBase: |
346 | - """Tests suitable for a target that restricts structural subscriptions.""" |
347 | +class StructuralSubscriptionTestBase: |
348 | |
349 | def setUp(self): |
350 | - super(RestrictedStructuralSubscriptionTestBase, self).setUp() |
351 | + super(StructuralSubscriptionTestBase, self).setUp() |
352 | self.ordinary_subscriber = self.factory.makePerson() |
353 | self.bug_supervisor_subscriber = self.factory.makePerson() |
354 | self.team_owner = self.factory.makePerson() |
355 | @@ -124,12 +124,7 @@ |
356 | self.ordinary_subscriber, self.ordinary_subscriber) |
357 | |
358 | |
359 | -class UnrestrictedStructuralSubscriptionTestBase( |
360 | - RestrictedStructuralSubscriptionTestBase): |
361 | - """ |
362 | - Tests suitable for a target that does not restrict structural |
363 | - subscriptions. |
364 | - """ |
365 | +class UnrestrictedStructuralSubscription(StructuralSubscriptionTestBase): |
366 | |
367 | def test_structural_subscription_by_ordinary_user(self): |
368 | # ordinary users can subscribe themselves |
369 | @@ -172,276 +167,198 @@ |
370 | None) |
371 | |
372 | |
373 | -class FilteredStructuralSubscriptionTestBase: |
374 | +class FilteredStructuralSubscriptionTestBase(StructuralSubscriptionTestBase): |
375 | """Tests for filtered structural subscriptions.""" |
376 | |
377 | - layer = LaunchpadFunctionalLayer |
378 | - |
379 | - def makeTarget(self): |
380 | - raise NotImplementedError(self.makeTarget) |
381 | - |
382 | def makeBugTask(self): |
383 | return self.factory.makeBugTask(target=self.target) |
384 | |
385 | - def setUp(self): |
386 | - super(FilteredStructuralSubscriptionTestBase, self).setUp() |
387 | - self.ordinary_subscriber = self.factory.makePerson() |
388 | - login_person(self.ordinary_subscriber) |
389 | - self.target = self.makeTarget() |
390 | - self.bugtask = self.makeBugTask() |
391 | - self.bug = self.bugtask.bug |
392 | - self.subscription = self.target.addSubscription( |
393 | - self.ordinary_subscriber, self.ordinary_subscriber) |
394 | - self.subscription.bug_notification_level = ( |
395 | - BugNotificationLevel.COMMENTS) |
396 | - |
397 | - def assertSubscriptions( |
398 | - self, expected_subscriptions, level=BugNotificationLevel.NOTHING): |
399 | - observed_subscriptions = list( |
400 | - self.target.getSubscriptionsForBugTask(self.bugtask, level)) |
401 | - self.assertEqual(expected_subscriptions, observed_subscriptions) |
402 | - |
403 | def test_getSubscriptionsForBugTask(self): |
404 | # If no one has a filtered subscription for the given bug, the result |
405 | # of getSubscriptionsForBugTask() is the same as for |
406 | # getSubscriptions(). |
407 | + bugtask = self.makeBugTask() |
408 | subscriptions = self.target.getSubscriptions( |
409 | min_bug_notification_level=BugNotificationLevel.NOTHING) |
410 | - self.assertSubscriptions(list(subscriptions)) |
411 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
412 | + bugtask, BugNotificationLevel.NOTHING) |
413 | + self.assertEqual(list(subscriptions), list(subscriptions_for_bugtask)) |
414 | |
415 | def test_getSubscriptionsForBugTask_with_filter_on_status(self): |
416 | # If a status filter exists for a subscription, the result of |
417 | # getSubscriptionsForBugTask() may be a subset of getSubscriptions(). |
418 | + bugtask = self.makeBugTask() |
419 | + |
420 | + # Create a new subscription on self.target. |
421 | + login_person(self.ordinary_subscriber) |
422 | + subscription = self.target.addSubscription( |
423 | + self.ordinary_subscriber, self.ordinary_subscriber) |
424 | + subscription.bug_notification_level = BugNotificationLevel.COMMENTS |
425 | |
426 | # Without any filters the subscription is found. |
427 | - self.assertSubscriptions([self.subscription]) |
428 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
429 | + bugtask, BugNotificationLevel.NOTHING) |
430 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
431 | |
432 | # Filter the subscription to bugs in the CONFIRMED state. |
433 | - subscription_filter = self.subscription.newBugFilter() |
434 | + subscription_filter = BugSubscriptionFilter() |
435 | + subscription_filter.structural_subscription = subscription |
436 | subscription_filter.statuses = [BugTaskStatus.CONFIRMED] |
437 | |
438 | # With the filter the subscription is not found. |
439 | - self.assertSubscriptions([]) |
440 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
441 | + bugtask, BugNotificationLevel.NOTHING) |
442 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
443 | |
444 | # If the filter is adjusted, the subscription is found again. |
445 | - subscription_filter.statuses = [self.bugtask.status] |
446 | - self.assertSubscriptions([self.subscription]) |
447 | + subscription_filter.statuses = [bugtask.status] |
448 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
449 | + bugtask, BugNotificationLevel.NOTHING) |
450 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
451 | |
452 | def test_getSubscriptionsForBugTask_with_filter_on_importance(self): |
453 | # If an importance filter exists for a subscription, the result of |
454 | # getSubscriptionsForBugTask() may be a subset of getSubscriptions(). |
455 | + bugtask = self.makeBugTask() |
456 | + |
457 | + # Create a new subscription on self.target. |
458 | + login_person(self.ordinary_subscriber) |
459 | + subscription = self.target.addSubscription( |
460 | + self.ordinary_subscriber, self.ordinary_subscriber) |
461 | + subscription.bug_notification_level = BugNotificationLevel.COMMENTS |
462 | |
463 | # Without any filters the subscription is found. |
464 | - self.assertSubscriptions([self.subscription]) |
465 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
466 | + bugtask, BugNotificationLevel.NOTHING) |
467 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
468 | |
469 | # Filter the subscription to bugs in the CRITICAL state. |
470 | - subscription_filter = self.subscription.newBugFilter() |
471 | + subscription_filter = BugSubscriptionFilter() |
472 | + subscription_filter.structural_subscription = subscription |
473 | subscription_filter.importances = [BugTaskImportance.CRITICAL] |
474 | |
475 | # With the filter the subscription is not found. |
476 | - self.assertSubscriptions([]) |
477 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
478 | + bugtask, BugNotificationLevel.NOTHING) |
479 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
480 | |
481 | # If the filter is adjusted, the subscription is found again. |
482 | - subscription_filter.importances = [self.bugtask.importance] |
483 | - self.assertSubscriptions([self.subscription]) |
484 | + subscription_filter.importances = [bugtask.importance] |
485 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
486 | + bugtask, BugNotificationLevel.NOTHING) |
487 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
488 | |
489 | def test_getSubscriptionsForBugTask_with_filter_on_level(self): |
490 | # All structural subscriptions have a level for bug notifications |
491 | # which getSubscriptionsForBugTask() observes. |
492 | + bugtask = self.makeBugTask() |
493 | |
494 | - # Adjust the subscription level to METADATA. |
495 | - self.subscription.bug_notification_level = ( |
496 | - BugNotificationLevel.METADATA) |
497 | + # Create a new METADATA level subscription on self.target. |
498 | + login_person(self.ordinary_subscriber) |
499 | + subscription = self.target.addSubscription( |
500 | + self.ordinary_subscriber, self.ordinary_subscriber) |
501 | + subscription.bug_notification_level = BugNotificationLevel.METADATA |
502 | |
503 | # The subscription is found when looking for NOTHING or above. |
504 | - self.assertSubscriptions( |
505 | - [self.subscription], BugNotificationLevel.NOTHING) |
506 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
507 | + bugtask, BugNotificationLevel.NOTHING) |
508 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
509 | # The subscription is found when looking for METADATA or above. |
510 | - self.assertSubscriptions( |
511 | - [self.subscription], BugNotificationLevel.METADATA) |
512 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
513 | + bugtask, BugNotificationLevel.METADATA) |
514 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
515 | # The subscription is not found when looking for COMMENTS or above. |
516 | - self.assertSubscriptions( |
517 | - [], BugNotificationLevel.COMMENTS) |
518 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
519 | + bugtask, BugNotificationLevel.COMMENTS) |
520 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
521 | |
522 | def test_getSubscriptionsForBugTask_with_filter_include_any_tags(self): |
523 | # If a subscription filter has include_any_tags, a bug with one or |
524 | # more tags is matched. |
525 | + bugtask = self.makeBugTask() |
526 | |
527 | - subscription_filter = self.subscription.newBugFilter() |
528 | + # Create a new subscription on self.target. |
529 | + login_person(self.ordinary_subscriber) |
530 | + subscription = self.target.addSubscription( |
531 | + self.ordinary_subscriber, self.ordinary_subscriber) |
532 | + subscription.bug_notification_level = BugNotificationLevel.COMMENTS |
533 | + subscription_filter = subscription.newBugFilter() |
534 | subscription_filter.include_any_tags = True |
535 | |
536 | # Without any tags the subscription is not found. |
537 | - self.assertSubscriptions([]) |
538 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
539 | + bugtask, BugNotificationLevel.NOTHING) |
540 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
541 | |
542 | # With any tag the subscription is found. |
543 | - self.bug.tags = ["foo"] |
544 | - self.assertSubscriptions([self.subscription]) |
545 | + bugtask.bug.tags = ["foo"] |
546 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
547 | + bugtask, BugNotificationLevel.NOTHING) |
548 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
549 | |
550 | def test_getSubscriptionsForBugTask_with_filter_exclude_any_tags(self): |
551 | # If a subscription filter has exclude_any_tags, only bugs with no |
552 | # tags are matched. |
553 | + bugtask = self.makeBugTask() |
554 | |
555 | - subscription_filter = self.subscription.newBugFilter() |
556 | + # Create a new subscription on self.target. |
557 | + login_person(self.ordinary_subscriber) |
558 | + subscription = self.target.addSubscription( |
559 | + self.ordinary_subscriber, self.ordinary_subscriber) |
560 | + subscription.bug_notification_level = BugNotificationLevel.COMMENTS |
561 | + subscription_filter = subscription.newBugFilter() |
562 | subscription_filter.exclude_any_tags = True |
563 | |
564 | # Without any tags the subscription is found. |
565 | - self.assertSubscriptions([self.subscription]) |
566 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
567 | + bugtask, BugNotificationLevel.NOTHING) |
568 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
569 | |
570 | # With any tag the subscription is not found. |
571 | - self.bug.tags = ["foo"] |
572 | - self.assertSubscriptions([]) |
573 | - |
574 | - def test_getSubscriptionsForBugTask_with_filter_for_any_tag(self): |
575 | - # If a subscription filter specifies that any of one or more specific |
576 | - # tags must be present, bugs with any of those tags are matched. |
577 | - |
578 | - # Looking for either the "foo" or the "bar" tag. |
579 | - subscription_filter = self.subscription.newBugFilter() |
580 | - subscription_filter.tags = [u"foo", u"bar"] |
581 | - subscription_filter.find_all_tags = False |
582 | - |
583 | - # Without either tag the subscription is not found. |
584 | - self.assertSubscriptions([]) |
585 | - |
586 | - # With either tag the subscription is found. |
587 | - self.bug.tags = ["bar", "baz"] |
588 | - self.assertSubscriptions([self.subscription]) |
589 | - |
590 | - def test_getSubscriptionsForBugTask_with_filter_for_all_tags(self): |
591 | - # If a subscription filter specifies that all of one or more specific |
592 | - # tags must be present, bugs with all of those tags are matched. |
593 | - |
594 | - # Looking for both the "foo" and the "bar" tag. |
595 | - subscription_filter = self.subscription.newBugFilter() |
596 | - subscription_filter.tags = [u"foo", u"bar"] |
597 | - subscription_filter.find_all_tags = True |
598 | - |
599 | - # Without either tag the subscription is not found. |
600 | - self.assertSubscriptions([]) |
601 | - |
602 | - # Without only one of the required tags the subscription is not found. |
603 | - self.bug.tags = ["foo"] |
604 | - self.assertSubscriptions([]) |
605 | - |
606 | - # With both required tags the subscription is found. |
607 | - self.bug.tags = ["foo", "bar"] |
608 | - self.assertSubscriptions([self.subscription]) |
609 | - |
610 | - def test_getSubscriptionsForBugTask_with_filter_for_not_any_tag(self): |
611 | - # If a subscription filter specifies that any of one or more specific |
612 | - # tags must not be present, bugs without any of those tags are |
613 | - # matched. |
614 | - |
615 | - # Looking to exclude the "foo" or "bar" tags. |
616 | - subscription_filter = self.subscription.newBugFilter() |
617 | - subscription_filter.tags = [u"-foo", u"-bar"] |
618 | - subscription_filter.find_all_tags = False |
619 | - |
620 | - # Without either tag the subscription is found. |
621 | - self.assertSubscriptions([self.subscription]) |
622 | - |
623 | - # With either tag the subscription is no longer found. |
624 | - self.bug.tags = ["foo"] |
625 | - self.assertSubscriptions([]) |
626 | - |
627 | - def test_getSubscriptionsForBugTask_with_filter_for_not_all_tags(self): |
628 | - # If a subscription filter specifies that all of one or more specific |
629 | - # tags must not be present, bugs without all of those tags are |
630 | - # matched. |
631 | - |
632 | - # Looking to exclude the "foo" and "bar" tags. |
633 | - subscription_filter = self.subscription.newBugFilter() |
634 | - subscription_filter.tags = [u"-foo", u"-bar"] |
635 | - subscription_filter.find_all_tags = True |
636 | - |
637 | - # Without either tag the subscription is found. |
638 | - self.assertSubscriptions([self.subscription]) |
639 | - |
640 | - # With only one of the excluded tags the subscription is found. |
641 | - self.bug.tags = ["foo"] |
642 | - self.assertSubscriptions([self.subscription]) |
643 | - |
644 | - # With both tags the subscription is no longer found. |
645 | - self.bug.tags = ["foo", "bar"] |
646 | - self.assertSubscriptions([]) |
647 | + bugtask.bug.tags = ["foo"] |
648 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
649 | + bugtask, BugNotificationLevel.NOTHING) |
650 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
651 | |
652 | def test_getSubscriptionsForBugTask_with_multiple_filters(self): |
653 | # If multiple filters exist for a subscription, all filters must |
654 | # match. |
655 | + bugtask = self.makeBugTask() |
656 | |
657 | - # Add the "foo" tag to the bug. |
658 | - self.bug.tags = ["foo"] |
659 | + # Create a new subscription on self.target. |
660 | + login_person(self.ordinary_subscriber) |
661 | + subscription = self.target.addSubscription( |
662 | + self.ordinary_subscriber, self.ordinary_subscriber) |
663 | + subscription.bug_notification_level = BugNotificationLevel.COMMENTS |
664 | |
665 | # Filter the subscription to bugs in the CRITICAL state. |
666 | - subscription_filter = self.subscription.newBugFilter() |
667 | + subscription_filter = BugSubscriptionFilter() |
668 | + subscription_filter.structural_subscription = subscription |
669 | subscription_filter.statuses = [BugTaskStatus.CONFIRMED] |
670 | subscription_filter.importances = [BugTaskImportance.CRITICAL] |
671 | |
672 | # With the filter the subscription is not found. |
673 | - self.assertSubscriptions([]) |
674 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
675 | + bugtask, BugNotificationLevel.NOTHING) |
676 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
677 | |
678 | # If the filter is adjusted to match status but not importance, the |
679 | # subscription is still not found. |
680 | - subscription_filter.statuses = [self.bugtask.status] |
681 | - self.assertSubscriptions([]) |
682 | + subscription_filter.statuses = [bugtask.status] |
683 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
684 | + bugtask, BugNotificationLevel.NOTHING) |
685 | + self.assertEqual([], list(subscriptions_for_bugtask)) |
686 | |
687 | # If the filter is adjusted to also match importance, the subscription |
688 | # is found again. |
689 | - subscription_filter.importances = [self.bugtask.importance] |
690 | - self.assertSubscriptions([self.subscription]) |
691 | - |
692 | - # If the filter is given some tag criteria, the subscription is not |
693 | - # found. |
694 | - subscription_filter.tags = [u"-foo", u"bar", u"baz"] |
695 | - subscription_filter.find_all_tags = False |
696 | - self.assertSubscriptions([]) |
697 | - |
698 | - # After removing the "foo" tag and adding the "bar" tag, the |
699 | - # subscription is found. |
700 | - self.bug.tags = ["bar"] |
701 | - self.assertSubscriptions([self.subscription]) |
702 | - |
703 | - # Requiring that all tag criteria are fulfilled causes the |
704 | - # subscription to no longer be found. |
705 | - subscription_filter.find_all_tags = True |
706 | - self.assertSubscriptions([]) |
707 | - |
708 | - # After adding the "baz" tag, the subscription is found again. |
709 | - self.bug.tags = ["bar", "baz"] |
710 | - self.assertSubscriptions([self.subscription]) |
711 | - |
712 | - def test_getSubscriptionsForBugTask_any_filter_is_a_match(self): |
713 | - # If a subscription has multiple filters, the subscription is selected |
714 | - # when any filter is found to match. Put another way, the filters are |
715 | - # ORed together. |
716 | - subscription_filter1 = self.subscription.newBugFilter() |
717 | - subscription_filter1.statuses = [BugTaskStatus.CONFIRMED] |
718 | - subscription_filter2 = self.subscription.newBugFilter() |
719 | - subscription_filter2.tags = [u"foo"] |
720 | - |
721 | - # With the filter the subscription is not found. |
722 | - self.assertSubscriptions([]) |
723 | - |
724 | - # If the bugtask is adjusted to match the criteria of the first filter |
725 | - # but not those of the second, the subscription is found. |
726 | - self.bugtask.transitionToStatus( |
727 | - BugTaskStatus.CONFIRMED, self.ordinary_subscriber) |
728 | - self.assertSubscriptions([self.subscription]) |
729 | - |
730 | - # If the filter is adjusted to also match the criteria of the second |
731 | - # filter, the subscription is still found. |
732 | - self.bugtask.bug.tags = [u"foo"] |
733 | - self.assertSubscriptions([self.subscription]) |
734 | - |
735 | - # If the bugtask is adjusted to no longer match the criteria of the |
736 | - # first filter, the subscription is still found. |
737 | - self.bugtask.transitionToStatus( |
738 | - BugTaskStatus.INPROGRESS, self.ordinary_subscriber) |
739 | - self.assertSubscriptions([self.subscription]) |
740 | + subscription_filter.importances = [bugtask.importance] |
741 | + subscriptions_for_bugtask = self.target.getSubscriptionsForBugTask( |
742 | + bugtask, BugNotificationLevel.NOTHING) |
743 | + self.assertEqual([subscription], list(subscriptions_for_bugtask)) |
744 | |
745 | |
746 | class TestStructuralSubscriptionForDistro( |
747 | - RestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
748 | + FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
749 | |
750 | layer = LaunchpadFunctionalLayer |
751 | |
752 | @@ -504,15 +421,10 @@ |
753 | StructuralSubscription) |
754 | |
755 | |
756 | -class TestStructuralSubscriptionFiltersForDistro( |
757 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
758 | - |
759 | - def makeTarget(self): |
760 | - return self.factory.makeDistribution() |
761 | - |
762 | - |
763 | class TestStructuralSubscriptionForProduct( |
764 | - UnrestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
765 | + UnrestrictedStructuralSubscription, |
766 | + FilteredStructuralSubscriptionTestBase, |
767 | + TestCaseWithFactory): |
768 | |
769 | layer = LaunchpadFunctionalLayer |
770 | |
771 | @@ -521,15 +433,10 @@ |
772 | self.target = self.factory.makeProduct() |
773 | |
774 | |
775 | -class TestStructuralSubscriptionFiltersForProduct( |
776 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
777 | - |
778 | - def makeTarget(self): |
779 | - return self.factory.makeProduct() |
780 | - |
781 | - |
782 | class TestStructuralSubscriptionForDistroSourcePackage( |
783 | - UnrestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
784 | + UnrestrictedStructuralSubscription, |
785 | + FilteredStructuralSubscriptionTestBase, |
786 | + TestCaseWithFactory): |
787 | |
788 | layer = LaunchpadFunctionalLayer |
789 | |
790 | @@ -539,15 +446,10 @@ |
791 | self.target = ProxyFactory(self.target) |
792 | |
793 | |
794 | -class TestStructuralSubscriptionFiltersForDistroSourcePackage( |
795 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
796 | - |
797 | - def makeTarget(self): |
798 | - return self.factory.makeDistributionSourcePackage() |
799 | - |
800 | - |
801 | class TestStructuralSubscriptionForMilestone( |
802 | - UnrestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
803 | + UnrestrictedStructuralSubscription, |
804 | + FilteredStructuralSubscriptionTestBase, |
805 | + TestCaseWithFactory): |
806 | |
807 | layer = LaunchpadFunctionalLayer |
808 | |
809 | @@ -556,19 +458,15 @@ |
810 | self.target = self.factory.makeMilestone() |
811 | self.target = ProxyFactory(self.target) |
812 | |
813 | - |
814 | -class TestStructuralSubscriptionFiltersForMilestone( |
815 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
816 | - |
817 | - def makeTarget(self): |
818 | - return self.factory.makeMilestone() |
819 | - |
820 | def makeBugTask(self): |
821 | + # XXX Should test with target *and* series_target. |
822 | return self.factory.makeBugTask(target=self.target.series_target) |
823 | |
824 | |
825 | class TestStructuralSubscriptionForDistroSeries( |
826 | - UnrestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
827 | + UnrestrictedStructuralSubscription, |
828 | + FilteredStructuralSubscriptionTestBase, |
829 | + TestCaseWithFactory): |
830 | |
831 | layer = LaunchpadFunctionalLayer |
832 | |
833 | @@ -578,15 +476,10 @@ |
834 | self.target = ProxyFactory(self.target) |
835 | |
836 | |
837 | -class TestStructuralSubscriptionFiltersForDistroSeries( |
838 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
839 | - |
840 | - def makeTarget(self): |
841 | - return self.factory.makeDistroSeries() |
842 | - |
843 | - |
844 | class TestStructuralSubscriptionForProjectGroup( |
845 | - UnrestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
846 | + UnrestrictedStructuralSubscription, |
847 | + FilteredStructuralSubscriptionTestBase, |
848 | + TestCaseWithFactory): |
849 | |
850 | layer = LaunchpadFunctionalLayer |
851 | |
852 | @@ -595,20 +488,15 @@ |
853 | self.target = self.factory.makeProject() |
854 | self.target = ProxyFactory(self.target) |
855 | |
856 | - |
857 | -class TestStructuralSubscriptionFiltersForProjectGroup( |
858 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
859 | - |
860 | - def makeTarget(self): |
861 | - return self.factory.makeProject() |
862 | - |
863 | def makeBugTask(self): |
864 | return self.factory.makeBugTask( |
865 | target=self.factory.makeProduct(project=self.target)) |
866 | |
867 | |
868 | class TestStructuralSubscriptionForProductSeries( |
869 | - UnrestrictedStructuralSubscriptionTestBase, TestCaseWithFactory): |
870 | + UnrestrictedStructuralSubscription, |
871 | + FilteredStructuralSubscriptionTestBase, |
872 | + TestCaseWithFactory): |
873 | |
874 | layer = LaunchpadFunctionalLayer |
875 | |
876 | @@ -618,13 +506,6 @@ |
877 | self.target = ProxyFactory(self.target) |
878 | |
879 | |
880 | -class TestStructuralSubscriptionFiltersForProductSeries( |
881 | - FilteredStructuralSubscriptionTestBase, TestCaseWithFactory): |
882 | - |
883 | - def makeTarget(self): |
884 | - return self.factory.makeProductSeries() |
885 | - |
886 | - |
887 | class TestStructuralSubscriptionTargetHelper(TestCaseWithFactory): |
888 | """Tests for implementations of `IStructuralSubscriptionTargetHelper`.""" |
889 |
Trivial.