Merge lp:~adeuring/launchpad/bug-829074 into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Abel Deuring
Approved revision: no longer in the source branch.
Merged at revision: 14748
Proposed branch: lp:~adeuring/launchpad/bug-829074
Merge into: lp:launchpad
Diff against target: 574 lines (+408/-94)
3 files modified
lib/lp/bugs/interfaces/bugtask.py (+3/-1)
lib/lp/bugs/model/bugtask.py (+186/-91)
lib/lp/bugs/tests/test_bugtask_search.py (+219/-2)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-829074
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Richard Harding (community) code* Approve
Review via email: mp+91302@code.launchpad.net

Commit message

[r=jcsackett,rharding][bug=829074][no-qa][incr] Allow to specify an upstream target in upstream related bug searches

Description of the change

This branch is a step to fix bug 829074: Show bugs that are
not known to affect "official" upstream.

Bryce suggests in a comment on the bug to optionally limit the
search to bugtasks targeted to the upstream product.

Daniel points out in another comment that bug 232545 describes
a very similar problem.

On the model side, we can fix both bugs if we allow to optionally
specify an upstream bug target; the search should then return bugs
that have (or do not have) a bug task with the "right properties"
for this target.

An oddity of upstream related searches is that a search with the
parameter pending_bugwatch_elsewhere considers distributions
as a possible upstream target, while the other upstream searches
consider only products as upstream targets.

While I don't see much value in using an entire distribtuion
as the new optional upstream target, I added this option
nevertheless for all upstream related searches, just for the sake
of consistency. (To address bug 232545 completely, I'll add the
targets ISourcePackage and IDistributionSourcePackage in a later
branch. This should make work easier if a Debian package is the
the upstream of an Ubuntu package; it might also help for the new
derived distributions.)

Implementation:

A new property BugTaskSearchParams.upstream_target. This property
  is only relevant if at least one of
  BugTaskSearchParams.pending_bugwatch_elsewhere,
  BugTaskSearchParams.has_no_upstream_bugtask,
  BugTaskSearchParams.resolved_usptream or
  BugTaskSearchParams.open_upstream is specified.

If BugTaskSearchParams.upstream_target is specified, the WHERE
clause returned by BugTaskSet.buildUpstreamClause() contains an
expression that limits the search to bugtasks for this target.

The file lp/bugs/tests/test_bugtask_search.py allows to write a
single test that is run for all possible bug targets.

A proper setup of an upstream related test can be a bit convoluted
-- see for example the already existing method
test_has_no_upstream_bugtask(). To make life a bit easier, I
limited the new tests to the targets SourcePackage and
DistributionSourcePackage: Searching for bugs on other targets
with an upstream filter together with a specific upstream target
is probably pointless, and I intend to add option "limit the
upstream search to upstream target X" to source packages and
DSPs as the main search target.

tests:

./bin/test bugs -vvt lp.bugs.tests.test_bugtask_search.*test_resolved_upstream
./bin/test bugs -vvt lp.bugs.tests.test_bugtask_search.*test_open_upstream
./bin/test bugs -vvt lp.bugs.tests.test_bugtask_search.*test_has_no_upstream_bugtask
./bin/test bugs -vvt lp.bugs.tests.test_bugtask_search.*test_pending_bugwatch_elsewhere

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

This looks solid from what I can tell.

review: Approve (code*)
Revision history for this message
j.c.sackett (jcsackett) wrote :

Abel--

This looks pretty good.

I'm marking approved, but I have just one request--could you add a comment on the UpstreamFilter test mixin you created noting it has to be used with SearchTestBase? Right now I see that it's no concern because of the evil test magic happening in the bottom of the file, but since we don't always use that test suite setup logic in other test files, I could see other developers getting confused on a refactor.

Other than that, I dig this--especially the breakup of that massive upstream query method into several smaller pieces. Thanks for this branch.

review: Approve
Revision history for this message
Abel Deuring (adeuring) wrote :

On 02.02.2012 20:16, j.c.sackett wrote:
> Review: Approve
>
> Abel--
>
> This looks pretty good.
>
> I'm marking approved, but I have just one request--could you add a comment on
> the UpstreamFilter test mixin you created noting it has to be used with
> SearchTestBase? Right now I see that it's no concern because of the evil test
> magic happening in the bottom of the file, but since we don't always use that
> test suite setup logic in other test files, I could see other developers getting
> confused on a refactor.

Good idea -- yes, mixing the wrong set of base classes can lead to weird
results or errors...

>
> Other than that, I dig this--especially the breakup of that massive upstream
> query method into several smaller pieces. Thanks for this branch.

Thanks :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
2--- lib/lp/bugs/interfaces/bugtask.py 2012-01-10 14:24:19 +0000
3+++ lib/lp/bugs/interfaces/bugtask.py 2012-02-02 21:57:28 +0000
4@@ -1204,7 +1204,8 @@
5 hardware_is_linked_to_bug=False,
6 linked_branches=None, linked_blueprints=None,
7 structural_subscriber=None, modified_since=None,
8- created_since=None, exclude_conjoined_tasks=False, cve=None):
9+ created_since=None, exclude_conjoined_tasks=False, cve=None,
10+ upstream_target=None):
11
12 self.bug = bug
13 self.searchtext = searchtext
14@@ -1254,6 +1255,7 @@
15 self.created_since = created_since
16 self.exclude_conjoined_tasks = exclude_conjoined_tasks
17 self.cve = cve
18+ self.upstream_target = upstream_target
19
20 def setProduct(self, product):
21 """Set the upstream context on which to filter the search."""
22
23=== modified file 'lib/lp/bugs/model/bugtask.py'
24--- lib/lp/bugs/model/bugtask.py 2012-01-09 13:25:03 +0000
25+++ lib/lp/bugs/model/bugtask.py 2012-02-02 21:57:28 +0000
26@@ -1742,19 +1742,35 @@
27 _ORDERBY_COLUMN = None
28
29 _open_resolved_upstream = """
30- EXISTS (
31- SELECT TRUE FROM BugTask AS RelatedBugTask
32- WHERE RelatedBugTask.bug = BugTask.bug
33- AND RelatedBugTask.id != BugTask.id
34- AND ((
35- RelatedBugTask.bugwatch IS NOT NULL AND
36- RelatedBugTask.status %s)
37- OR (
38- RelatedBugTask.product IS NOT NULL AND
39- RelatedBugTask.bugwatch IS NULL AND
40- RelatedBugTask.status %s))
41- )
42- """
43+ EXISTS (
44+ SELECT TRUE FROM BugTask AS RelatedBugTask
45+ WHERE RelatedBugTask.bug = BugTask.bug
46+ AND RelatedBugTask.id != BugTask.id
47+ AND ((
48+ RelatedBugTask.bugwatch IS NOT NULL AND
49+ RelatedBugTask.status %s)
50+ OR (
51+ RelatedBugTask.product IS NOT NULL AND
52+ RelatedBugTask.bugwatch IS NULL AND
53+ RelatedBugTask.status %s))
54+ )
55+ """
56+
57+ _open_resolved_upstream_with_target = """
58+ EXISTS (
59+ SELECT TRUE FROM BugTask AS RelatedBugTask
60+ WHERE RelatedBugTask.bug = BugTask.bug
61+ AND RelatedBugTask.id != BugTask.id
62+ AND ((
63+ RelatedBugTask.%(target_column)s = %(target_id)s AND
64+ RelatedBugTask.bugwatch IS NOT NULL AND
65+ RelatedBugTask.status %(status_with_watch)s)
66+ OR (
67+ RelatedBugTask.%(target_column)s = %(target_id)s AND
68+ RelatedBugTask.bugwatch IS NULL AND
69+ RelatedBugTask.status %(status_without_watch)s))
70+ )
71+ """
72
73 title = "A set of bug tasks"
74
75@@ -2411,66 +2427,147 @@
76 query, clauseTables, decorator, join_tables,
77 has_duplicate_results, with_clause)
78
79- def buildUpstreamClause(self, params):
80- """Return an clause for returning upstream data if the data exists.
81-
82- This method will handles BugTasks that do not have upstream BugTasks
83- as well as thoses that do.
84+ def buildPendingBugwatchElsewhereClause(self, params):
85+ """Return a clause for BugTaskSearchParams.pending_bugwatch_elsewhere
86 """
87- params = self._require_params(params)
88- upstream_clauses = []
89- if params.pending_bugwatch_elsewhere:
90- if params.product:
91- # Include only bugtasks that do no have bug watches that
92- # belong to a product that does not use Malone.
93- pending_bugwatch_elsewhere_clause = """
94- EXISTS (
95- SELECT TRUE
96- FROM BugTask AS RelatedBugTask
97- LEFT OUTER JOIN Product AS OtherProduct
98- ON RelatedBugTask.product = OtherProduct.id
99- WHERE RelatedBugTask.bug = BugTask.bug
100- AND RelatedBugTask.id = BugTask.id
101- AND RelatedBugTask.bugwatch IS NULL
102- AND OtherProduct.official_malone IS FALSE
103- AND RelatedBugTask.status != %s)
104- """ % sqlvalues(BugTaskStatus.INVALID)
105+ if params.product:
106+ # Include only bugtasks that do no have bug watches that
107+ # belong to a product that does not use Malone.
108+ return """
109+ EXISTS (
110+ SELECT TRUE
111+ FROM BugTask AS RelatedBugTask
112+ LEFT OUTER JOIN Product AS OtherProduct
113+ ON RelatedBugTask.product = OtherProduct.id
114+ WHERE RelatedBugTask.bug = BugTask.bug
115+ AND RelatedBugTask.id = BugTask.id
116+ AND RelatedBugTask.bugwatch IS NULL
117+ AND OtherProduct.official_malone IS FALSE
118+ AND RelatedBugTask.status != %s)
119+ """ % sqlvalues(BugTaskStatus.INVALID)
120+ elif params.upstream_target is None:
121+ # Include only bugtasks that have other bugtasks on targets
122+ # not using Malone, which are not Invalid, and have no bug
123+ # watch.
124+ return """
125+ EXISTS (
126+ SELECT TRUE
127+ FROM BugTask AS RelatedBugTask
128+ LEFT OUTER JOIN Distribution AS OtherDistribution
129+ ON RelatedBugTask.distribution =
130+ OtherDistribution.id
131+ LEFT OUTER JOIN Product AS OtherProduct
132+ ON RelatedBugTask.product = OtherProduct.id
133+ WHERE RelatedBugTask.bug = BugTask.bug
134+ AND RelatedBugTask.id != BugTask.id
135+ AND RelatedBugTask.bugwatch IS NULL
136+ AND (
137+ OtherDistribution.official_malone IS FALSE
138+ OR OtherProduct.official_malone IS FALSE)
139+ AND RelatedBugTask.status != %s)
140+ """ % sqlvalues(BugTaskStatus.INVALID)
141+ else:
142+ # Include only bugtasks that have other bugtasks on
143+ # params.upstream_target, but only if this this product
144+ # does not use Malone and if the bugtasks are not Invalid,
145+ # and have no bug watch.
146+ if IProduct.providedBy(params.upstream_target):
147+ target_clause = 'RelatedBugTask.product = %s'
148+ elif IDistribution.providedBy(params.upstream_target):
149+ target_clause = 'RelatedBugTask.distribution = %s'
150 else:
151- # Include only bugtasks that have other bugtasks on targets
152- # not using Malone, which are not Invalid, and have no bug
153- # watch.
154- pending_bugwatch_elsewhere_clause = """
155- EXISTS (
156- SELECT TRUE
157- FROM BugTask AS RelatedBugTask
158- LEFT OUTER JOIN Distribution AS OtherDistribution
159- ON RelatedBugTask.distribution =
160- OtherDistribution.id
161- LEFT OUTER JOIN Product AS OtherProduct
162- ON RelatedBugTask.product = OtherProduct.id
163- WHERE RelatedBugTask.bug = BugTask.bug
164- AND RelatedBugTask.id != BugTask.id
165- AND RelatedBugTask.bugwatch IS NULL
166- AND (
167- OtherDistribution.official_malone IS FALSE
168- OR OtherProduct.official_malone IS FALSE)
169- AND RelatedBugTask.status != %s)
170- """ % sqlvalues(BugTaskStatus.INVALID)
171-
172- upstream_clauses.append(pending_bugwatch_elsewhere_clause)
173-
174- if params.has_no_upstream_bugtask:
175+ raise AssertionError(
176+ 'params.upstream_target must be a Distribution or '
177+ 'a Product')
178+ # There is no point to construct a real sub-select if we
179+ # already know that the result will be empty.
180+ if params.upstream_target.official_malone:
181+ return 'false'
182+ target_clause = target_clause % sqlvalues(
183+ params.upstream_target.id)
184+ return """
185+ EXISTS (
186+ SELECT TRUE
187+ FROM BugTask AS RelatedBugTask
188+ WHERE RelatedBugTask.bug = BugTask.bug
189+ AND RelatedBugTask.id != BugTask.id
190+ AND RelatedBugTask.bugwatch IS NULL
191+ AND %s
192+ AND RelatedBugTask.status != %s)
193+ """ % (target_clause, sqlvalues(BugTaskStatus.INVALID)[0])
194+
195+ def buildNoUpstreamBugtaskClause(self, params):
196+ """Return a clause for BugTaskSearchParams.has_no_upstream_bugtask."""
197+ if params.upstream_target is None:
198 # Find all bugs that has no product bugtask. We limit the
199 # SELECT by matching against BugTask.bug to make the query
200 # faster.
201- has_no_upstream_bugtask_clause = """
202+ return """
203 NOT EXISTS (SELECT TRUE
204 FROM BugTask AS OtherBugTask
205 WHERE OtherBugTask.bug = BugTask.bug
206 AND OtherBugTask.product IS NOT NULL)
207 """
208- upstream_clauses.append(has_no_upstream_bugtask_clause)
209-
210+ elif IProduct.providedBy(params.upstream_target):
211+ return """
212+ NOT EXISTS (SELECT TRUE
213+ FROM BugTask AS OtherBugTask
214+ WHERE OtherBugTask.bug = BugTask.bug
215+ AND OtherBugTask.product=%s)
216+ """ % sqlvalues(params.upstream_target.id)
217+ elif IDistribution.providedBy(params.upstream_target):
218+ return """
219+ NOT EXISTS (SELECT TRUE
220+ FROM BugTask AS OtherBugTask
221+ WHERE OtherBugTask.bug = BugTask.bug
222+ AND OtherBugTask.distribution=%s)
223+ """ % sqlvalues(params.upstream_target.id)
224+ else:
225+ raise AssertionError(
226+ 'params.upstream_target must be a Distribution or '
227+ 'a Product')
228+
229+ def buildOpenOrResolvedUpstreamClause(self, params,
230+ statuses_for_watch_tasks,
231+ statuses_for_upstream_tasks):
232+ """Return a clause for BugTaskSearchParams.open_upstream or
233+ BugTaskSearchParams.resolved_upstream."""
234+ if params.upstream_target is None:
235+ return self._open_resolved_upstream % (
236+ search_value_to_where_condition(
237+ any(*statuses_for_watch_tasks)),
238+ search_value_to_where_condition(
239+ any(*statuses_for_upstream_tasks)))
240+ elif IProduct.providedBy(params.upstream_target):
241+ query_values = {'target_column': 'product'}
242+ elif IDistribution.providedBy(params.upstream_target):
243+ query_values = {'target_column': 'distribution'}
244+ else:
245+ raise AssertionError(
246+ 'params.upstream_target must be a Distribution or '
247+ 'a Product')
248+ query_values['target_id'] = sqlvalues(params.upstream_target.id)[0]
249+ query_values['status_with_watch'] = search_value_to_where_condition(
250+ any(*statuses_for_watch_tasks))
251+ query_values['status_without_watch'] = search_value_to_where_condition(
252+ any(*statuses_for_upstream_tasks))
253+ return self._open_resolved_upstream_with_target % query_values
254+
255+ def buildOpenUpstreamClause(self, params):
256+ """Return a clause for BugTaskSearchParams.open_upstream."""
257+ statuses_for_open_tasks = [
258+ BugTaskStatus.NEW,
259+ BugTaskStatus.INCOMPLETE,
260+ BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
261+ BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
262+ BugTaskStatus.CONFIRMED,
263+ BugTaskStatus.INPROGRESS,
264+ BugTaskStatus.UNKNOWN]
265+ return self.buildOpenOrResolvedUpstreamClause(
266+ params, statuses_for_open_tasks, statuses_for_open_tasks)
267+
268+ def buildResolvedUpstreamClause(self, params):
269+ """Return a clause for BugTaskSearchParams.open_upstream."""
270 # Our definition of "resolved upstream" means:
271 #
272 # * bugs with bugtasks linked to watches that are invalid,
273@@ -2481,36 +2578,34 @@
274 # This definition of "resolved upstream" should address the use
275 # cases we gathered at UDS Paris (and followup discussions with
276 # seb128, sfllaw, et al.)
277+ statuses_for_watch_tasks = [
278+ BugTaskStatus.INVALID,
279+ BugTaskStatus.FIXCOMMITTED,
280+ BugTaskStatus.FIXRELEASED]
281+ statuses_for_upstream_tasks = [
282+ BugTaskStatus.FIXCOMMITTED,
283+ BugTaskStatus.FIXRELEASED]
284+ return self.buildOpenOrResolvedUpstreamClause(
285+ params, statuses_for_watch_tasks, statuses_for_upstream_tasks)
286+
287+ def buildUpstreamClause(self, params):
288+ """Return an clause for returning upstream data if the data exists.
289+
290+ This method will handles BugTasks that do not have upstream BugTasks
291+ as well as thoses that do.
292+ """
293+ params = self._require_params(params)
294+ upstream_clauses = []
295+ if params.pending_bugwatch_elsewhere:
296+ upstream_clauses.append(
297+ self.buildPendingBugwatchElsewhereClause(params))
298+ if params.has_no_upstream_bugtask:
299+ upstream_clauses.append(
300+ self.buildNoUpstreamBugtaskClause(params))
301 if params.resolved_upstream:
302- statuses_for_watch_tasks = [
303- BugTaskStatus.INVALID,
304- BugTaskStatus.FIXCOMMITTED,
305- BugTaskStatus.FIXRELEASED]
306- statuses_for_upstream_tasks = [
307- BugTaskStatus.FIXCOMMITTED,
308- BugTaskStatus.FIXRELEASED]
309-
310- only_resolved_upstream_clause = self._open_resolved_upstream % (
311- search_value_to_where_condition(
312- any(*statuses_for_watch_tasks)),
313- search_value_to_where_condition(
314- any(*statuses_for_upstream_tasks)))
315- upstream_clauses.append(only_resolved_upstream_clause)
316+ upstream_clauses.append(self.buildResolvedUpstreamClause(params))
317 if params.open_upstream:
318- statuses_for_open_tasks = [
319- BugTaskStatus.NEW,
320- BugTaskStatus.INCOMPLETE,
321- BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE,
322- BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE,
323- BugTaskStatus.CONFIRMED,
324- BugTaskStatus.INPROGRESS,
325- BugTaskStatus.UNKNOWN]
326- only_open_upstream_clause = self._open_resolved_upstream % (
327- search_value_to_where_condition(
328- any(*statuses_for_open_tasks)),
329- search_value_to_where_condition(
330- any(*statuses_for_open_tasks)))
331- upstream_clauses.append(only_open_upstream_clause)
332+ upstream_clauses.append(self.buildOpenUpstreamClause(params))
333
334 if upstream_clauses:
335 upstream_clause = " OR ".join(upstream_clauses)
336
337=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
338--- lib/lp/bugs/tests/test_bugtask_search.py 2012-01-03 10:57:01 +0000
339+++ lib/lp/bugs/tests/test_bugtask_search.py 2012-02-02 21:57:28 +0000
340@@ -1228,7 +1228,223 @@
341 self.assertSearchFinds(params, [self.bugtasks[-1]])
342
343
344-class SourcePackageTarget(BugTargetTestBase):
345+class UpstreamFilterTests:
346+ """A mixin class with tests related to restircted upstream filtering.
347+
348+ Classes derived from this class must also derive from SearchTestBase.
349+
350+ These tests make sense only for the targets SourcePackage
351+ DistributionSourcePackage.
352+ """
353+
354+ def setUpUpstreamTests(self, upstream_target):
355+ # The default test bugs have two tasks for DistributionSourcePackage
356+ # tests: one task for the DSP and another task for a product;
357+ # they have three tasks for SourcePackage tests: for a product,
358+ # for a DSP and for a sourcepackage.
359+ # Tests in this class are about searching bug tasks, where the
360+ # bug has a task for any upstream target or for a given upstream
361+ # target and where the bug task for the upstream target has certain
362+ # properties.
363+ with person_logged_in(self.searchtarget.distribution.owner):
364+ self.searchtarget.distribution.official_malone = True
365+ for existing_task in self.bugtasks:
366+ bug = existing_task.bug
367+ self.factory.makeBugTask(bug, target=upstream_target)
368+
369+ def addWatch(self, bug, target=None):
370+ # Add a bug watch to the bugtask for the given target. If no
371+ # target is specified, the bug watch is added to the default
372+ # bugtask, which is a different product for each bug.
373+ if target is None:
374+ task = bug.bugtasks[0]
375+ else:
376+ for task in bug.bugtasks:
377+ if task.target == target:
378+ break
379+ with person_logged_in(task.target.owner):
380+ watch = self.factory.makeBugWatch(bug=bug)
381+ task.bugwatch = watch
382+
383+ def test_pending_bugwatch_elsewhere__no_upstream_specified(self):
384+ # By default, those bugs are returned where
385+ # - an upstream task exists
386+ # - the upstream product does not use LP for bug tracking
387+ # - the bug task has no bug watch.
388+ # All test bugs fulfill this condition.
389+ upstream_target = self.factory.makeProduct()
390+ self.setUpUpstreamTests(upstream_target)
391+ params = self.getBugTaskSearchParams(
392+ user=None, pending_bugwatch_elsewhere=True)
393+ self.assertSearchFinds(params, self.bugtasks)
394+ # If a bug watch is added to only one of the product related
395+ # bug tasks, the bug is still returned.
396+ self.addWatch(self.bugtasks[0].bug)
397+ self.addWatch(self.bugtasks[1].bug, target=upstream_target)
398+ self.assertSearchFinds(params, self.bugtasks)
399+ # If bugwatches are added to the other product related bug task
400+ # too, the bugs are not included in the search result.
401+ self.addWatch(self.bugtasks[0].bug, target=upstream_target)
402+ self.addWatch(self.bugtasks[1].bug)
403+ self.assertSearchFinds(params, self.bugtasks[2:])
404+
405+ def test_pending_bugwatch_elsewhere__upstream_product(self):
406+ # If an upstream target using Malone is specified, a search
407+ # returns all bugs with a bug task for this target, if the
408+ # task does not have a bug watch.
409+ upstream_target = self.factory.makeProduct()
410+ self.setUpUpstreamTests(upstream_target)
411+ # The first bug task of all test bugs is targeted to its
412+ # own Product instance.
413+ bug = self.bugtasks[0].bug
414+ single_bugtask_product = bug.bugtasks[0].target
415+ params = self.getBugTaskSearchParams(
416+ user=None, pending_bugwatch_elsewhere=True,
417+ upstream_target=single_bugtask_product)
418+ self.assertSearchFinds(params, self.bugtasks[:1])
419+ # If a bug watch is added to this task, the search returns an
420+ # empty result set.
421+ self.addWatch(self.bugtasks[0].bug)
422+ self.assertSearchFinds(params, [])
423+
424+ def test_pending_bugwatch_elsewhere__upstream_product_uses_lp(self):
425+ # If an upstream target not using Malone is specified, a search
426+ # alsways returns an empty result set.
427+ upstream_target = self.factory.makeProduct()
428+ self.setUpUpstreamTests(upstream_target)
429+ with person_logged_in(upstream_target.owner):
430+ upstream_target.official_malone = True
431+ params = self.getBugTaskSearchParams(
432+ user=None, pending_bugwatch_elsewhere=True,
433+ upstream_target=upstream_target)
434+ self.assertSearchFinds(params, [])
435+
436+ def test_pending_bugwatch_elsewhere__upstream_distribution(self):
437+ # If an upstream target not using Malone is specified, a search
438+ # alsways returns an empty result set.
439+ upstream_target = self.factory.makeDistribution()
440+ self.setUpUpstreamTests(upstream_target)
441+ params = self.getBugTaskSearchParams(
442+ user=None, pending_bugwatch_elsewhere=True,
443+ upstream_target=upstream_target)
444+ self.assertSearchFinds(params, self.bugtasks)
445+
446+ def test_has_no_upstream_bugtask__target_specified(self):
447+ # The target of the default bugtask of the first test bug
448+ # (a product) does not appear in other bugs, thus a search
449+ # returns all other bugtasks if we specify the search parameters
450+ # has_no_upstream_bugtask and use the target described above
451+ # as the upstream_target.
452+ bug = self.bugtasks[0].bug
453+ upstream_target = bug.bugtasks[0].target
454+ params = self.getBugTaskSearchParams(
455+ user=None, has_no_upstream_bugtask=True,
456+ upstream_target=upstream_target)
457+ self.assertSearchFinds(params, self.bugtasks[1:])
458+ # If a new distribution is specified as the upstream target,
459+ # all bugs are returned, since there are no tasks for this
460+ # distribution.
461+ upstream_target = self.factory.makeDistribution()
462+ params = self.getBugTaskSearchParams(
463+ user=None, has_no_upstream_bugtask=True,
464+ upstream_target=upstream_target)
465+ self.assertSearchFinds(params, self.bugtasks)
466+ # When we add bugtasks for this distribution, the search returns
467+ # an empty result.
468+ self.setUpUpstreamTests(upstream_target)
469+ self.assertSearchFinds(params, [])
470+
471+ def test_open_upstream(self):
472+ # It is possible to search for bugs with open upstream bugtasks.
473+ bug = self.bugtasks[2].bug
474+ upstream_task = bug.bugtasks[0]
475+ upstream_owner = upstream_task.target.owner
476+ with person_logged_in(upstream_owner):
477+ upstream_task.transitionToStatus(
478+ BugTaskStatus.FIXRELEASED, upstream_owner)
479+ params = self.getBugTaskSearchParams(user=None, open_upstream=True)
480+ self.assertSearchFinds(params, self.bugtasks[:2])
481+
482+ def test_open_upstream__upstream_product_specified(self):
483+ # A search for bugs having an open upstream bugtask can be
484+ # limited to a specific upstream product.
485+ bug = self.bugtasks[2].bug
486+ upstream_task = bug.bugtasks[0]
487+ upstream_product = upstream_task.target
488+ params = self.getBugTaskSearchParams(
489+ user=None, open_upstream=True, upstream_target=upstream_product)
490+ self.assertSearchFinds(params, self.bugtasks[2:])
491+ upstream_owner = upstream_product.owner
492+ with person_logged_in(upstream_owner):
493+ upstream_task.transitionToStatus(
494+ BugTaskStatus.FIXRELEASED, upstream_owner)
495+ self.assertSearchFinds(params, [])
496+
497+ def test_open_upstream__upstream_distribution_specified(self):
498+ # A search for bugs having an open upstream bugtask can be
499+ # limited to a specific upstream distribution.
500+ upstream_distro = self.factory.makeDistribution()
501+ params = self.getBugTaskSearchParams(
502+ user=None, open_upstream=True, upstream_target=upstream_distro)
503+ self.assertSearchFinds(params, [])
504+ bug = self.bugtasks[0].bug
505+ distro_task = self.factory.makeBugTask(
506+ bug=bug, target=upstream_distro)
507+ self.assertSearchFinds(params, self.bugtasks[:1])
508+ with person_logged_in(upstream_distro.owner):
509+ distro_task.transitionToStatus(
510+ BugTaskStatus.FIXRELEASED, upstream_distro.owner)
511+ self.assertSearchFinds(params, [])
512+
513+ def test_resolved_upstream(self):
514+ # It is possible to search for bugs with resolved upstream bugtasks.
515+ bug = self.bugtasks[2].bug
516+ upstream_task = bug.bugtasks[0]
517+ upstream_owner = upstream_task.target.owner
518+ with person_logged_in(upstream_owner):
519+ upstream_task.transitionToStatus(
520+ BugTaskStatus.FIXRELEASED, upstream_owner)
521+ params = self.getBugTaskSearchParams(user=None, resolved_upstream=True)
522+ self.assertSearchFinds(params, self.bugtasks[2:])
523+
524+ def test_resolved_upstream__upstream_product_specified(self):
525+ # A search for bugs having a resolved upstream bugtask can be
526+ # limited to a specific upstream product.
527+ bug = self.bugtasks[2].bug
528+ upstream_task = bug.bugtasks[0]
529+ upstream_product = upstream_task.target
530+ params = self.getBugTaskSearchParams(
531+ user=None, resolved_upstream=True,
532+ upstream_target=upstream_product)
533+ self.assertSearchFinds(params, [])
534+ upstream_owner = upstream_product.owner
535+ for bug in [task.bug for task in self.bugtasks]:
536+ upstream_task = bug.bugtasks[0]
537+ upstream_owner = upstream_task.owner
538+ with person_logged_in(upstream_owner):
539+ upstream_task.transitionToStatus(
540+ BugTaskStatus.FIXRELEASED, upstream_owner)
541+ self.assertSearchFinds(params, self.bugtasks[2:])
542+
543+ def test_resolved_upstream__upstream_distribution_specified(self):
544+ # A search for bugs having an open upstream bugtask can be
545+ # limited to a specific upstream distribution.
546+ upstream_distro = self.factory.makeDistribution()
547+ params = self.getBugTaskSearchParams(
548+ user=None, resolved_upstream=True,
549+ upstream_target=upstream_distro)
550+ self.assertSearchFinds(params, [])
551+ bug = self.bugtasks[0].bug
552+ distro_task = self.factory.makeBugTask(
553+ bug=bug, target=upstream_distro)
554+ self.assertSearchFinds(params, [])
555+ with person_logged_in(upstream_distro.owner):
556+ distro_task.transitionToStatus(
557+ BugTaskStatus.FIXRELEASED, upstream_distro.owner)
558+ self.assertSearchFinds(params, self.bugtasks[:1])
559+
560+
561+class SourcePackageTarget(BugTargetTestBase, UpstreamFilterTests):
562 """Use a source package as the bug target."""
563
564 def setUp(self):
565@@ -1275,7 +1491,8 @@
566
567
568 class DistributionSourcePackageTarget(BugTargetTestBase,
569- BugTargetWithBugSuperVisor):
570+ BugTargetWithBugSuperVisor,
571+ UpstreamFilterTests):
572 """Use a distribution source package as the bug target."""
573
574 def setUp(self):