Merge lp:~adeuring/launchpad/bug-594247-unittests-for-searchtasks-4 into lp:launchpad

Proposed by Abel Deuring
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 11864
Proposed branch: lp:~adeuring/launchpad/bug-594247-unittests-for-searchtasks-4
Merge into: lp:launchpad
Diff against target: 733 lines (+302/-115)
4 files modified
lib/lp/bugs/interfaces/bugtask.py (+2/-2)
lib/lp/bugs/tests/test_bugtask_search.py (+267/-113)
lib/lp/testing/factory.py (+11/-0)
lib/lp/testing/tests/test_factory.py (+22/-0)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-594247-unittests-for-searchtasks-4
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+39991@code.launchpad.net

Commit message

unit tests of BugtaskSet.search() and BugTaskSet.searchBugIds(): new method SearchTestBase.assertSearchFinds(); more tests; more robust lookup of a bug target that is not the main target of the tests.

Description of the change

new attempt, hopefully without merge conflict and an overly huge diff...

This branch adds more unit tests for BugTaskSet.search() and for
BugTaskSet.searchBugIds(), leaving only a few parameters of
BugTaskSearchParams not covered.

Aside for these additional tests, I added a new method
assertSearchFinds() (suggested by Gavin in a previous review
of these tests), which makes reading the tests slightly less
boring and a bit more readable. Working on this change, I
noticed that one tests missed an assert...

Working on tests to find bugs being created or modified after a
given time, I noticed that it was possible to pass the parameters
created_since and modified_since to the constructor of
BugTaskSearchparams, but that the object properties created_since
and modified_since were always set to None. This is now fixed.

One test needed access to a product which is not the main
target of the current test; an already existing test modifies
the bug task of this "other target"
(changeStatusOfBugTaskForOtherProduct()). I moved the code to find
this "other bugtask" into a separate method
(findBugtaskForOtherProduct()). The implementation is less obsucre
ini comparison with the old implementation to find the "other
bugtask".

test: ./bin/test -vvt test_bugtask_search

no lint.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

Sorry for taking so long to review this. Reading unit tests can be
hard work :-/

A general comment is that the findBugtaskForOtherProduct and its
helper _findBugtaskForOtherProduct started to get a bit convoluted. I
don't have a suggestion for making it better though. I think I
wouldn't have separated it out into two methods; I would have just
overridden the method and called up, but that's a matter of
preference.

It's great to have some clear and accurate definitions of search
behaviour.

+1

[1]

+ # Return the bugtask for the product that not related to the
+ # main bug target.

s/that/that is/

[2]

+ def _findBugtaskForOtherProduct(self, bugtask, main_product):

To summarize this, just to check my understanding:

  Return the first bugtask of the given bugtask's bug that is (a)
  targeted to an IProduct and (b) not targeted to main_product.

[3]

+ # Search results can be limited to bugs with a bug target to which
+ # a given person has a structural subscription.

Oh my, I was not aware of this. This could get complicated with
filters. For now I'm going to ignore it :)

[4]

+ def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
+ # Change the status of another bugtask of the same bug to the
+ # given status.
...
+ bug = bugtask.bug
+ for other_task in bug.bugtasks:
+ other_target = other_task.target
+ if IProduct.providedBy(other_target):
+ with person_logged_in(other_target.owner):
+ other_task.transitionToStatus(
+ new_status, other_target.owner)

This will change the status of bugtask too, which might be fine but is
not what is implied by the method name and comment.

If you only wish to change the status of other tasks, the
related_tasks property could be useful:

    for other_task in bugtask.related_tasks:
        other_target = other_task.target
        if IProduct.providedBy(other_target):
            with person_logged_in(other_target.owner):
                other_task.transitionToStatus(
                    new_status, other_target.owner)

[5]

+ def makeCVE(self, sequence, description=None,
+ cvestate=CveStatus.CANDIDATE):

This probably ought to have a simple test or two. I only just noticed
that there are tests for the factory methods.

review: Approve

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 2010-11-03 14:31:33 +0000
3+++ lib/lp/bugs/interfaces/bugtask.py 2010-11-04 15:56:18 +0000
4@@ -1188,8 +1188,8 @@
5 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
6 self.linked_branches = linked_branches
7 self.structural_subscriber = structural_subscriber
8- self.modified_since = None
9- self.created_since = None
10+ self.modified_since = modified_since
11+ self.created_since = created_since
12
13 def setProduct(self, product):
14 """Set the upstream context on which to filter the search."""
15
16=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
17--- lib/lp/bugs/tests/test_bugtask_search.py 2010-10-29 13:00:57 +0000
18+++ lib/lp/bugs/tests/test_bugtask_search.py 2010-11-04 15:56:18 +0000
19@@ -25,6 +25,7 @@
20
21 from lp.bugs.interfaces.bugattachment import BugAttachmentType
22 from lp.bugs.interfaces.bugtask import (
23+ BugBranchSearch,
24 BugTaskImportance,
25 BugTaskSearchParams,
26 BugTaskStatus,
27@@ -53,29 +54,30 @@
28 super(SearchTestBase, self).setUp()
29 self.bugtask_set = getUtility(IBugTaskSet)
30
31+ def assertSearchFinds(self, params, expected_bugtasks):
32+ # Run a search for the given search parameters and check if
33+ # the result matches the expected bugtasks.
34+ search_result = self.runSearch(params)
35+ expected = self.resultValuesForBugtasks(expected_bugtasks)
36+ self.assertEqual(expected, search_result)
37+
38 def test_search_all_bugtasks_for_target(self):
39 # BugTaskSet.search() returns all bug tasks for a given bug
40 # target, if only the bug target is passed as a search parameter.
41 params = self.getBugTaskSearchParams(user=None)
42- search_result = self.runSearch(params)
43- expected = self.resultValuesForBugtasks(self.bugtasks)
44- self.assertEqual(expected, search_result)
45+ self.assertSearchFinds(params, self.bugtasks)
46
47 def test_private_bug_in_search_result(self):
48 # Private bugs are not included in search results for anonymous users.
49 with person_logged_in(self.owner):
50 self.bugtasks[-1].bug.setPrivate(True, self.owner)
51 params = self.getBugTaskSearchParams(user=None)
52- search_result = self.runSearch(params)
53- expected = self.resultValuesForBugtasks(self.bugtasks)[:-1]
54- self.assertEqual(expected, search_result)
55+ self.assertSearchFinds(params, self.bugtasks[:-1])
56
57 # Private bugs are not included in search results for ordinary users.
58 user = self.factory.makePerson()
59 params = self.getBugTaskSearchParams(user=user)
60- search_result = self.runSearch(params)
61- expected = self.resultValuesForBugtasks(self.bugtasks)[:-1]
62- self.assertEqual(expected, search_result)
63+ self.assertSearchFinds(params, self.bugtasks[:-1])
64
65 # If the user is subscribed to the bug, it is included in the
66 # search result.
67@@ -83,16 +85,12 @@
68 with person_logged_in(self.owner):
69 self.bugtasks[-1].bug.subscribe(user, self.owner)
70 params = self.getBugTaskSearchParams(user=user)
71- search_result = self.runSearch(params)
72- expected = self.resultValuesForBugtasks(self.bugtasks)
73- self.assertEqual(expected, search_result)
74+ self.assertSearchFinds(params, self.bugtasks)
75
76 # Private bugs are included in search results for admins.
77 admin = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')
78 params = self.getBugTaskSearchParams(user=admin)
79- search_result = self.runSearch(params)
80- expected = self.resultValuesForBugtasks(self.bugtasks)
81- self.assertEqual(expected, search_result)
82+ self.assertSearchFinds(params, self.bugtasks)
83
84 def test_search_by_bug_reporter(self):
85 # Search results can be limited to bugs filed by a given person.
86@@ -100,9 +98,7 @@
87 reporter = bugtask.bug.owner
88 params = self.getBugTaskSearchParams(
89 user=None, bug_reporter=reporter)
90- search_result = self.runSearch(params)
91- expected = self.resultValuesForBugtasks([bugtask])
92- self.assertEqual(expected, search_result)
93+ self.assertSearchFinds(params, [bugtask])
94
95 def test_search_by_bug_commenter(self):
96 # Search results can be limited to bugs having a comment from a
97@@ -118,9 +114,7 @@
98 expected.bug.newMessage(owner=commenter, content='a comment')
99 params = self.getBugTaskSearchParams(
100 user=None, bug_commenter=commenter)
101- search_result = self.runSearch(params)
102- expected = self.resultValuesForBugtasks([expected])
103- self.assertEqual(expected, search_result)
104+ self.assertSearchFinds(params, [expected])
105
106 def test_search_by_person_affected_by_bug(self):
107 # Search results can be limited to bugs which affect a given person.
108@@ -130,9 +124,7 @@
109 expected.bug.markUserAffected(affected_user)
110 params = self.getBugTaskSearchParams(
111 user=None, affected_user=affected_user)
112- search_result = self.runSearch(params)
113- expected = self.resultValuesForBugtasks([expected])
114- self.assertEqual(expected, search_result)
115+ self.assertSearchFinds(params, [expected])
116
117 def test_search_by_bugtask_assignee(self):
118 # Search results can be limited to bugtask assigned to a given
119@@ -142,9 +134,7 @@
120 with person_logged_in(assignee):
121 expected.transitionToAssignee(assignee)
122 params = self.getBugTaskSearchParams(user=None, assignee=assignee)
123- search_result = self.runSearch(params)
124- expected = self.resultValuesForBugtasks([expected])
125- self.assertEqual(expected, search_result)
126+ self.assertSearchFinds(params, [expected])
127
128 def test_search_by_bug_subscriber(self):
129 # Search results can be limited to bugs to which a given person
130@@ -154,9 +144,69 @@
131 with person_logged_in(subscriber):
132 expected.bug.subscribe(subscriber, subscribed_by=subscriber)
133 params = self.getBugTaskSearchParams(user=None, subscriber=subscriber)
134- search_result = self.runSearch(params)
135- expected = self.resultValuesForBugtasks([expected])
136- self.assertEqual(expected, search_result)
137+ self.assertSearchFinds(params, [expected])
138+
139+ def subscribeToTarget(self, subscriber):
140+ # Subscribe the given person to the search target.
141+ with person_logged_in(subscriber):
142+ self.searchtarget.addSubscription(
143+ subscriber, subscribed_by=subscriber)
144+
145+ def _findBugtaskForOtherProduct(self, bugtask, main_product):
146+ # Return the bugtask for the product that is not related to the
147+ # main bug target.
148+ #
149+ # The default bugtasks of this test suite are created by
150+ # ObjectFactory.makeBugTask() as follows:
151+ # - a new bug is created having a new product as the target.
152+ # - another bugtask is created for self.searchtarget (or,
153+ # when self.searchtarget is a milestone, for the product
154+ # of the milestone)
155+ # This method returns the bug task for the product that is not
156+ # related to the main bug target.
157+ bug = bugtask.bug
158+ for other_task in bug.bugtasks:
159+ other_target = other_task.target
160+ if (IProduct.providedBy(other_target)
161+ and other_target != main_product):
162+ return other_task
163+ self.fail(
164+ 'No bug task found for a product that is not the target of '
165+ 'the main test bugtask.')
166+
167+ def findBugtaskForOtherProduct(self, bugtask):
168+ # Return the bugtask for the product that is not related to the
169+ # main bug target.
170+ #
171+ # This method must ober overridden for product related tests.
172+ return self._findBugtaskForOtherProduct(bugtask, None)
173+
174+ def test_search_by_structural_subscriber(self):
175+ # Search results can be limited to bugs with a bug target to which
176+ # a given person has a structural subscription.
177+ subscriber = self.factory.makePerson()
178+ # If the given person is not subscribed, no bugtasks are returned.
179+ params = self.getBugTaskSearchParams(
180+ user=None, structural_subscriber=subscriber)
181+ self.assertSearchFinds(params, [])
182+ # When the person is subscribed, all bugtasks are returned.
183+ self.subscribeToTarget(subscriber)
184+ params = self.getBugTaskSearchParams(
185+ user=None, structural_subscriber=subscriber)
186+ self.assertSearchFinds(params, self.bugtasks)
187+
188+ # Searching for a structural subscriber does not return a bugtask,
189+ # if the person is subscribed to another target than the main
190+ # bug target.
191+ other_subscriber = self.factory.makePerson()
192+ other_bugtask = self.findBugtaskForOtherProduct(self.bugtasks[0])
193+ other_target = other_bugtask.target
194+ with person_logged_in(other_subscriber):
195+ other_target.addSubscription(
196+ other_subscriber, subscribed_by=other_subscriber)
197+ params = self.getBugTaskSearchParams(
198+ user=None, structural_subscriber=other_subscriber)
199+ self.assertSearchFinds(params, [])
200
201 def test_search_by_bug_attachment(self):
202 # Search results can be limited to bugs having attachments of
203@@ -171,23 +221,17 @@
204 # We can search for bugs with non-patch attachments...
205 params = self.getBugTaskSearchParams(
206 user=None, attachmenttype=BugAttachmentType.UNSPECIFIED)
207- search_result = self.runSearch(params)
208- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
209- self.assertEqual(expected, search_result)
210+ self.assertSearchFinds(params, self.bugtasks[:1])
211 # ... for bugs with patches...
212 params = self.getBugTaskSearchParams(
213 user=None, attachmenttype=BugAttachmentType.PATCH)
214- search_result = self.runSearch(params)
215- expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
216- self.assertEqual(expected, search_result)
217+ self.assertSearchFinds(params, self.bugtasks[1:2])
218 # and for bugs with patches or attachments
219 params = self.getBugTaskSearchParams(
220 user=None, attachmenttype=any(
221 BugAttachmentType.PATCH,
222 BugAttachmentType.UNSPECIFIED))
223- search_result = self.runSearch(params)
224- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
225- self.assertEqual(expected, search_result)
226+ self.assertSearchFinds(params, self.bugtasks[:2])
227
228 def setUpFullTextSearchTests(self):
229 # Set text fields indexed by Bug.fti, BugTask.fti or
230@@ -205,40 +249,30 @@
231 self.setUpFullTextSearchTests()
232 params = self.getBugTaskSearchParams(
233 user=None, searchtext='one title')
234- search_result = self.runSearch(params)
235- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
236- self.assertEqual(expected, search_result)
237+ self.assertSearchFinds(params, self.bugtasks[:1])
238 # ... by BugTask.fti ...
239 params = self.getBugTaskSearchParams(
240 user=None, searchtext='two explanation')
241- search_result = self.runSearch(params)
242- expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
243- self.assertEqual(expected, search_result)
244+ self.assertSearchFinds(params, self.bugtasks[1:2])
245 # ...and by MessageChunk.fti
246 params = self.getBugTaskSearchParams(
247 user=None, searchtext='three comment')
248- search_result = self.runSearch(params)
249- expected = self.resultValuesForBugtasks(self.bugtasks[2:3])
250- self.assertEqual(expected, search_result)
251+ self.assertSearchFinds(params, self.bugtasks[2:3])
252
253 def test_fast_fulltext_search(self):
254 # Fast full text searches find text indexed by Bug.fti...
255 self.setUpFullTextSearchTests()
256 params = self.getBugTaskSearchParams(
257 user=None, fast_searchtext='one title')
258- search_result = self.runSearch(params)
259- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
260- self.assertEqual(expected, search_result)
261+ self.assertSearchFinds(params, self.bugtasks[:1])
262 # ... but not text indexed by BugTask.fti ...
263 params = self.getBugTaskSearchParams(
264 user=None, fast_searchtext='two explanation')
265- search_result = self.runSearch(params)
266- self.assertEqual([], search_result)
267+ self.assertSearchFinds(params, [])
268 # ..or by MessageChunk.fti
269 params = self.getBugTaskSearchParams(
270 user=None, fast_searchtext='three comment')
271- search_result = self.runSearch(params)
272- self.assertEqual([], search_result)
273+ self.assertSearchFinds(params, [])
274
275 def test_has_no_upstream_bugtask(self):
276 # Search results can be limited to bugtasks of bugs that do
277@@ -258,13 +292,13 @@
278 IDistributionSourcePackage.providedBy(self.searchtarget)):
279 if IDistribution.providedBy(self.searchtarget):
280 bug = self.factory.makeBug(distribution=self.searchtarget)
281- expected = self.resultValuesForBugtasks([bug.default_bugtask])
282+ expected = [bug.default_bugtask]
283 else:
284 bug = self.factory.makeBug(
285 distribution=self.searchtarget.distribution)
286 bugtask = self.factory.makeBugTask(
287 bug=bug, target=self.searchtarget)
288- expected = self.resultValuesForBugtasks([bugtask])
289+ expected = [bugtask]
290 else:
291 # Bugs without distribution related bugtasks have always at
292 # least one product related bugtask, hence a
293@@ -273,23 +307,14 @@
294 expected = []
295 params = self.getBugTaskSearchParams(
296 user=None, has_no_upstream_bugtask=True)
297- search_result = self.runSearch(params)
298- self.assertEqual(expected, search_result)
299+ self.assertSearchFinds(params, expected)
300
301 def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
302 # Change the status of another bugtask of the same bug to the
303 # given status.
304- bug = bugtask.bug
305- for other_task in bug.bugtasks:
306- other_target = other_task.target
307- if other_task != bugtask and IProduct.providedBy(other_target):
308- with person_logged_in(other_target.owner):
309- other_task.transitionToStatus(
310- new_status, other_target.owner)
311- return
312- self.fail(
313- 'No bug task found for a product that is not the target of '
314- 'the main test bugtask.')
315+ other_task = self.findBugtaskForOtherProduct(bugtask)
316+ with person_logged_in(other_task.target.owner):
317+ other_task.transitionToStatus(new_status, other_task.target.owner)
318
319 def test_upstream_status(self):
320 # Search results can be filtered by the status of an upstream
321@@ -299,14 +324,11 @@
322 # with status NEW for the "other" product, hence all bug tasks
323 # will be returned in a search for bugs that are open upstream.
324 params = self.getBugTaskSearchParams(user=None, open_upstream=True)
325- search_result = self.runSearch(params)
326- expected = self.resultValuesForBugtasks(self.bugtasks)
327- self.assertEqual(expected, search_result)
328+ self.assertSearchFinds(params, self.bugtasks)
329 # A search for tasks resolved upstream does not yield any bugtask.
330 params = self.getBugTaskSearchParams(
331 user=None, resolved_upstream=True)
332- search_result = self.runSearch(params)
333- self.assertEqual([], search_result)
334+ self.assertSearchFinds(params, [])
335 # But if we set upstream bug tasks to "fix committed" or "fix
336 # released", the related bug tasks for our test target appear in
337 # the search result.
338@@ -314,14 +336,11 @@
339 self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
340 self.changeStatusOfBugTaskForOtherProduct(
341 self.bugtasks[1], BugTaskStatus.FIXRELEASED)
342- search_result = self.runSearch(params)
343- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
344- self.assertEqual(expected, search_result)
345+ self.assertSearchFinds(params, self.bugtasks[:2])
346 # A search for bug tasks open upstream now returns only one
347 # test task.
348 params = self.getBugTaskSearchParams(user=None, open_upstream=True)
349- search_result = self.runSearch(params)
350- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
351+ self.assertSearchFinds(params, self.bugtasks[2:])
352
353 def test_tags(self):
354 # Search results can be limited to bugs having given tags.
355@@ -330,44 +349,31 @@
356 self.bugtasks[1].bug.tags = ['tag1', 'tag3']
357 params = self.getBugTaskSearchParams(
358 user=None, tag=any('tag2', 'tag3'))
359- search_result = self.runSearch(params)
360- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
361- self.assertEqual(expected, search_result)
362+ self.assertSearchFinds(params, self.bugtasks[:2])
363
364 params = self.getBugTaskSearchParams(
365 user=None, tag=all('tag2', 'tag3'))
366- search_result = self.runSearch(params)
367- self.assertEqual([], search_result)
368+ self.assertSearchFinds(params, [])
369
370 params = self.getBugTaskSearchParams(
371 user=None, tag=all('tag1', 'tag3'))
372- search_result = self.runSearch(params)
373- expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
374- self.assertEqual(expected, search_result)
375+ self.assertSearchFinds(params, self.bugtasks[1:2])
376
377 params = self.getBugTaskSearchParams(
378 user=None, tag=all('tag1', '-tag3'))
379- search_result = self.runSearch(params)
380- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
381- self.assertEqual(expected, search_result)
382+ self.assertSearchFinds(params, self.bugtasks[:1])
383
384 params = self.getBugTaskSearchParams(
385 user=None, tag=all('-tag1'))
386- search_result = self.runSearch(params)
387- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
388- self.assertEqual(expected, search_result)
389+ self.assertSearchFinds(params, self.bugtasks[2:])
390
391 params = self.getBugTaskSearchParams(
392 user=None, tag=all('*'))
393- search_result = self.runSearch(params)
394- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
395- self.assertEqual(expected, search_result)
396+ self.assertSearchFinds(params, self.bugtasks[:2])
397
398 params = self.getBugTaskSearchParams(
399 user=None, tag=all('-*'))
400- search_result = self.runSearch(params)
401- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
402- self.assertEqual(expected, search_result)
403+ self.assertSearchFinds(params, self.bugtasks[2:])
404
405 def test_date_closed(self):
406 # Search results can be filtered by the date_closed time
407@@ -379,13 +385,106 @@
408 self.assertTrue(utc_now >= self.bugtasks[2].date_closed)
409 params = self.getBugTaskSearchParams(
410 user=None, date_closed=greater_than(utc_now-timedelta(days=1)))
411- search_result = self.runSearch(params)
412- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
413- self.assertEqual(expected, search_result)
414+ self.assertSearchFinds(params, self.bugtasks[2:])
415 params = self.getBugTaskSearchParams(
416 user=None, date_closed=greater_than(utc_now+timedelta(days=1)))
417- search_result = self.runSearch(params)
418- self.assertEqual([], search_result)
419+ self.assertSearchFinds(params, [])
420+
421+ def test_created_since(self):
422+ # Search results can be limited to bugtasks created after a
423+ # given time.
424+ one_day_ago = self.bugtasks[0].datecreated - timedelta(days=1)
425+ two_days_ago = self.bugtasks[0].datecreated - timedelta(days=2)
426+ with person_logged_in(self.owner):
427+ self.bugtasks[0].datecreated = two_days_ago
428+ params = self.getBugTaskSearchParams(
429+ user=None, created_since=one_day_ago)
430+ self.assertSearchFinds(params, self.bugtasks[1:])
431+
432+ def test_modified_since(self):
433+ # Search results can be limited to bugs modified after a
434+ # given time.
435+ one_day_ago = (
436+ self.bugtasks[0].bug.date_last_updated - timedelta(days=1))
437+ two_days_ago = (
438+ self.bugtasks[0].bug.date_last_updated - timedelta(days=2))
439+ with person_logged_in(self.owner):
440+ self.bugtasks[0].bug.date_last_updated = two_days_ago
441+ params = self.getBugTaskSearchParams(
442+ user=None, modified_since=one_day_ago)
443+ self.assertSearchFinds(params, self.bugtasks[1:])
444+
445+ def test_branches_linked(self):
446+ # Search results can be limited to bugs with or without linked
447+ # branches.
448+ with person_logged_in(self.owner):
449+ branch = self.factory.makeBranch()
450+ self.bugtasks[0].bug.linkBranch(branch, self.owner)
451+ params = self.getBugTaskSearchParams(
452+ user=None, linked_branches=BugBranchSearch.BUGS_WITH_BRANCHES)
453+ self.assertSearchFinds(params, self.bugtasks[:1])
454+ params = self.getBugTaskSearchParams(
455+ user=None, linked_branches=BugBranchSearch.BUGS_WITHOUT_BRANCHES)
456+ self.assertSearchFinds(params, self.bugtasks[1:])
457+
458+ def test_limit_search_to_one_bug(self):
459+ # Search results can be limited to a given bug.
460+ params = self.getBugTaskSearchParams(
461+ user=None, bug=self.bugtasks[0].bug)
462+ self.assertSearchFinds(params, self.bugtasks[:1])
463+ other_bug = self.factory.makeBug()
464+ params = self.getBugTaskSearchParams(user=None, bug=other_bug)
465+ self.assertSearchFinds(params, [])
466+
467+ def test_filter_by_status(self):
468+ # Search results can be limited to bug tasks with a given status.
469+ params = self.getBugTaskSearchParams(
470+ user=None, status=BugTaskStatus.FIXCOMMITTED)
471+ self.assertSearchFinds(params, self.bugtasks[2:])
472+ params = self.getBugTaskSearchParams(
473+ user=None, status=any(BugTaskStatus.NEW, BugTaskStatus.TRIAGED))
474+ self.assertSearchFinds(params, self.bugtasks[:2])
475+ params = self.getBugTaskSearchParams(
476+ user=None, status=BugTaskStatus.WONTFIX)
477+ self.assertSearchFinds(params, [])
478+
479+ def test_filter_by_importance(self):
480+ # Search results can be limited to bug tasks with a given importance.
481+ params = self.getBugTaskSearchParams(
482+ user=None, importance=BugTaskImportance.HIGH)
483+ self.assertSearchFinds(params, self.bugtasks[:1])
484+ params = self.getBugTaskSearchParams(
485+ user=None,
486+ importance=any(BugTaskImportance.HIGH, BugTaskImportance.LOW))
487+ self.assertSearchFinds(params, self.bugtasks[:2])
488+ params = self.getBugTaskSearchParams(
489+ user=None, importance=BugTaskImportance.MEDIUM)
490+ self.assertSearchFinds(params, [])
491+
492+ def test_omit_duplicate_bugs(self):
493+ # Duplicate bugs can optionally be excluded from search results.
494+ # The default behaviour is to include duplicates.
495+ duplicate_bug = self.bugtasks[0].bug
496+ master_bug = self.bugtasks[1].bug
497+ with person_logged_in(self.owner):
498+ duplicate_bug.markAsDuplicate(master_bug)
499+ params = self.getBugTaskSearchParams(user=None)
500+ self.assertSearchFinds(params, self.bugtasks)
501+ # If we explicitly pass the parameter omit_duplicates=False, we get
502+ # the same result.
503+ params = self.getBugTaskSearchParams(user=None, omit_dupes=False)
504+ self.assertSearchFinds(params, self.bugtasks)
505+ # If omit_duplicates is set to True, the first task bug is omitted.
506+ params = self.getBugTaskSearchParams(user=None, omit_dupes=True)
507+ self.assertSearchFinds(params, self.bugtasks[1:])
508+
509+ def test_has_cve(self):
510+ # Search results can be limited to bugs linked to a CVE.
511+ with person_logged_in(self.owner):
512+ cve = self.factory.makeCVE('2010-0123')
513+ self.bugtasks[0].bug.linkCVE(cve, self.owner)
514+ params = self.getBugTaskSearchParams(user=None, has_cve=True)
515+ self.assertSearchFinds(params, self.bugtasks[:1])
516
517
518 class ProductAndDistributionTests:
519@@ -405,9 +504,7 @@
520 self.bugtasks[0].bug.addNomination(nominator, series1)
521 self.bugtasks[1].bug.addNomination(nominator, series2)
522 params = self.getBugTaskSearchParams(user=None, nominated_for=series1)
523- search_result = self.runSearch(params)
524- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
525- self.assertEqual(expected, search_result)
526+ self.assertSearchFinds(params, self.bugtasks[:1])
527
528
529 class BugTargetTestBase:
530@@ -445,15 +542,12 @@
531 supervisor = self.factory.makeTeam(owner=self.owner)
532 params = self.getBugTaskSearchParams(
533 user=None, bug_supervisor=supervisor)
534- search_result = self.runSearch(params)
535- self.assertEqual([], search_result)
536+ self.assertSearchFinds(params, [])
537
538 # If we appoint a bug supervisor, searching for bug tasks
539 # by supervisor will return all bugs for our test target.
540 self.setSupervisor(supervisor)
541- search_result = self.runSearch(params)
542- expected = self.resultValuesForBugtasks(self.bugtasks)
543- self.assertEqual(expected, search_result)
544+ self.assertSearchFinds(params, self.bugtasks)
545
546 def setSupervisor(self, supervisor):
547 """Set the bug supervisor for the bug task target."""
548@@ -484,6 +578,11 @@
549 """See `ProductAndDistributionTests`."""
550 return self.factory.makeProductSeries(product=self.searchtarget)
551
552+ def findBugtaskForOtherProduct(self, bugtask):
553+ # Return the bugtask for the product that is not related to the
554+ # main bug target.
555+ return self._findBugtaskForOtherProduct(bugtask, self.searchtarget)
556+
557
558 class ProductSeriesTarget(BugTargetTestBase):
559 """Use a product series as the bug target."""
560@@ -503,6 +602,31 @@
561 params.setProductSeries(self.searchtarget)
562 return params
563
564+ def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
565+ # Change the status of another bugtask of the same bug to the
566+ # given status.
567+ #
568+ # This method is called by SearchTestBase.test_upstream_status().
569+ # A search for bugs which are open or closed upstream has an
570+ # odd behaviour when the search target is a product series: In
571+ # this case, all bugs with an open or closed bug task for _any_
572+ # product are returned, including bug tasks for the main product
573+ # of the series. Hence we must set the status for all products
574+ # in order to avoid a failure of test_upstream_status().
575+ bug = bugtask.bug
576+ for other_task in bugtask.related_tasks:
577+ other_target = other_task.target
578+ if IProduct.providedBy(other_target):
579+ with person_logged_in(other_target.owner):
580+ other_task.transitionToStatus(
581+ new_status, other_target.owner)
582+
583+ def findBugtaskForOtherProduct(self, bugtask):
584+ # Return the bugtask for the product that not related to the
585+ # main bug target.
586+ return self._findBugtaskForOtherProduct(
587+ bugtask, self.searchtarget.product)
588+
589
590 class ProjectGroupTarget(BugTargetTestBase, BugTargetWithBugSuperVisor):
591 """Use a project group as the bug target."""
592@@ -525,8 +649,10 @@
593 def makeBugTasks(self):
594 """Create bug tasks for the search target."""
595 self.bugtasks = []
596+ self.products = []
597 with person_logged_in(self.owner):
598 product = self.factory.makeProduct(owner=self.owner)
599+ self.products.append(product)
600 product.project = self.searchtarget
601 self.bugtasks.append(
602 self.factory.makeBugTask(target=product))
603@@ -535,6 +661,7 @@
604 BugTaskStatus.TRIAGED, self.owner)
605
606 product = self.factory.makeProduct(owner=self.owner)
607+ self.products.append(product)
608 product.project = self.searchtarget
609 self.bugtasks.append(
610 self.factory.makeBugTask(target=product))
611@@ -543,6 +670,7 @@
612 BugTaskStatus.NEW, self.owner)
613
614 product = self.factory.makeProduct(owner=self.owner)
615+ self.products.append(product)
616 product.project = self.searchtarget
617 self.bugtasks.append(
618 self.factory.makeBugTask(target=product))
619@@ -557,6 +685,19 @@
620 for bugtask in self.bugtasks:
621 bugtask.target.setBugSupervisor(supervisor, self.owner)
622
623+ def findBugtaskForOtherProduct(self, bugtask):
624+ # Return the bugtask for the product that not related to the
625+ # main bug target.
626+ bug = bugtask.bug
627+ for other_task in bug.bugtasks:
628+ other_target = other_task.target
629+ if (IProduct.providedBy(other_target)
630+ and other_target not in self.products):
631+ return other_task
632+ self.fail(
633+ 'No bug task found for a product that is not the target of '
634+ 'the main test bugtask.')
635+
636
637 class MilestoneTarget(BugTargetTestBase):
638 """Use a milestone as the bug target."""
639@@ -583,6 +724,11 @@
640 for bugtask in self.bugtasks:
641 bugtask.transitionToMilestone(self.searchtarget, self.owner)
642
643+ def findBugtaskForOtherProduct(self, bugtask):
644+ # Return the bugtask for the product that not related to the
645+ # main bug target.
646+ return self._findBugtaskForOtherProduct(bugtask, self.product)
647+
648
649 class DistributionTarget(BugTargetTestBase, ProductAndDistributionTests,
650 BugTargetWithBugSuperVisor):
651@@ -645,6 +791,14 @@
652 params.setSourcePackage(self.searchtarget)
653 return params
654
655+ def subscribeToTarget(self, subscriber):
656+ # Subscribe the given person to the search target.
657+ # Source packages do not support structural subscriptions,
658+ # so we subscribe to the distro series instead.
659+ with person_logged_in(subscriber):
660+ self.searchtarget.distroseries.addSubscription(
661+ subscriber, subscribed_by=subscriber)
662+
663
664 class DistributionSourcePackageTarget(BugTargetTestBase,
665 BugTargetWithBugSuperVisor):
666
667=== modified file 'lib/lp/testing/factory.py'
668--- lib/lp/testing/factory.py 2010-11-03 23:44:56 +0000
669+++ lib/lp/testing/factory.py 2010-11-04 15:56:18 +0000
670@@ -111,6 +111,10 @@
671 IBugTrackerSet,
672 )
673 from lp.bugs.interfaces.bugwatch import IBugWatchSet
674+from lp.bugs.interfaces.cve import (
675+ CveStatus,
676+ ICveSet,
677+ )
678 from lp.buildmaster.enums import (
679 BuildFarmJobType,
680 BuildStatus,
681@@ -3230,6 +3234,13 @@
682 consumer, reviewed_by=owner, access_level=access_level)
683 return request_token.createAccessToken()
684
685+ def makeCVE(self, sequence, description=None,
686+ cvestate=CveStatus.CANDIDATE):
687+ """Create a new CVE record."""
688+ if description is None:
689+ description = self.getUniqueString()
690+ return getUtility(ICveSet).new(sequence, description, cvestate)
691+
692
693 # Some factory methods return simple Python types. We don't add
694 # security wrappers for them, as well as for objects created by
695
696=== modified file 'lib/lp/testing/tests/test_factory.py'
697--- lib/lp/testing/tests/test_factory.py 2010-10-25 19:11:46 +0000
698+++ lib/lp/testing/tests/test_factory.py 2010-11-04 15:56:18 +0000
699@@ -17,6 +17,10 @@
700 DatabaseFunctionalLayer,
701 LaunchpadZopelessLayer,
702 )
703+from lp.bugs.interfaces.cve import (
704+ CveStatus,
705+ ICve,
706+ )
707 from lp.buildmaster.enums import BuildStatus
708 from lp.code.enums import (
709 BranchType,
710@@ -492,6 +496,24 @@
711 ssp = self.factory.makeSuiteSourcePackage()
712 self.assertThat(ssp, ProvidesAndIsProxied(ISuiteSourcePackage))
713
714+ # makeCVE
715+ def test_makeCVE_returns_cve(self):
716+ cve = self.factory.makeCVE(sequence='2000-1234')
717+ self.assertThat(cve, ProvidesAndIsProxied(ICve))
718+
719+ def test_makeCVE_uses_sequence(self):
720+ cve = self.factory.makeCVE(sequence='2000-1234')
721+ self.assertEqual('2000-1234', cve.sequence)
722+
723+ def test_makeCVE_uses_description(self):
724+ cve = self.factory.makeCVE(sequence='2000-1234', description='foo')
725+ self.assertEqual('foo', cve.description)
726+
727+ def test_makeCVE_uses_cve_status(self):
728+ cve = self.factory.makeCVE(
729+ sequence='2000-1234', cvestate=CveStatus.DEPRECATED)
730+ self.assertEqual(CveStatus.DEPRECATED, cve.status)
731+
732
733 class TestFactoryWithLibrarian(TestCaseWithFactory):
734