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
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-11-03 14:31:33 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2010-11-04 15:56:18 +0000
@@ -1188,8 +1188,8 @@
1188 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug1188 self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
1189 self.linked_branches = linked_branches1189 self.linked_branches = linked_branches
1190 self.structural_subscriber = structural_subscriber1190 self.structural_subscriber = structural_subscriber
1191 self.modified_since = None1191 self.modified_since = modified_since
1192 self.created_since = None1192 self.created_since = created_since
11931193
1194 def setProduct(self, product):1194 def setProduct(self, product):
1195 """Set the upstream context on which to filter the search."""1195 """Set the upstream context on which to filter the search."""
11961196
=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py 2010-10-29 13:00:57 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py 2010-11-04 15:56:18 +0000
@@ -25,6 +25,7 @@
2525
26from lp.bugs.interfaces.bugattachment import BugAttachmentType26from lp.bugs.interfaces.bugattachment import BugAttachmentType
27from lp.bugs.interfaces.bugtask import (27from lp.bugs.interfaces.bugtask import (
28 BugBranchSearch,
28 BugTaskImportance,29 BugTaskImportance,
29 BugTaskSearchParams,30 BugTaskSearchParams,
30 BugTaskStatus,31 BugTaskStatus,
@@ -53,29 +54,30 @@
53 super(SearchTestBase, self).setUp()54 super(SearchTestBase, self).setUp()
54 self.bugtask_set = getUtility(IBugTaskSet)55 self.bugtask_set = getUtility(IBugTaskSet)
5556
57 def assertSearchFinds(self, params, expected_bugtasks):
58 # Run a search for the given search parameters and check if
59 # the result matches the expected bugtasks.
60 search_result = self.runSearch(params)
61 expected = self.resultValuesForBugtasks(expected_bugtasks)
62 self.assertEqual(expected, search_result)
63
56 def test_search_all_bugtasks_for_target(self):64 def test_search_all_bugtasks_for_target(self):
57 # BugTaskSet.search() returns all bug tasks for a given bug65 # BugTaskSet.search() returns all bug tasks for a given bug
58 # target, if only the bug target is passed as a search parameter.66 # target, if only the bug target is passed as a search parameter.
59 params = self.getBugTaskSearchParams(user=None)67 params = self.getBugTaskSearchParams(user=None)
60 search_result = self.runSearch(params)68 self.assertSearchFinds(params, self.bugtasks)
61 expected = self.resultValuesForBugtasks(self.bugtasks)
62 self.assertEqual(expected, search_result)
6369
64 def test_private_bug_in_search_result(self):70 def test_private_bug_in_search_result(self):
65 # Private bugs are not included in search results for anonymous users.71 # Private bugs are not included in search results for anonymous users.
66 with person_logged_in(self.owner):72 with person_logged_in(self.owner):
67 self.bugtasks[-1].bug.setPrivate(True, self.owner)73 self.bugtasks[-1].bug.setPrivate(True, self.owner)
68 params = self.getBugTaskSearchParams(user=None)74 params = self.getBugTaskSearchParams(user=None)
69 search_result = self.runSearch(params)75 self.assertSearchFinds(params, self.bugtasks[:-1])
70 expected = self.resultValuesForBugtasks(self.bugtasks)[:-1]
71 self.assertEqual(expected, search_result)
7276
73 # Private bugs are not included in search results for ordinary users.77 # Private bugs are not included in search results for ordinary users.
74 user = self.factory.makePerson()78 user = self.factory.makePerson()
75 params = self.getBugTaskSearchParams(user=user)79 params = self.getBugTaskSearchParams(user=user)
76 search_result = self.runSearch(params)80 self.assertSearchFinds(params, self.bugtasks[:-1])
77 expected = self.resultValuesForBugtasks(self.bugtasks)[:-1]
78 self.assertEqual(expected, search_result)
7981
80 # If the user is subscribed to the bug, it is included in the82 # If the user is subscribed to the bug, it is included in the
81 # search result.83 # search result.
@@ -83,16 +85,12 @@
83 with person_logged_in(self.owner):85 with person_logged_in(self.owner):
84 self.bugtasks[-1].bug.subscribe(user, self.owner)86 self.bugtasks[-1].bug.subscribe(user, self.owner)
85 params = self.getBugTaskSearchParams(user=user)87 params = self.getBugTaskSearchParams(user=user)
86 search_result = self.runSearch(params)88 self.assertSearchFinds(params, self.bugtasks)
87 expected = self.resultValuesForBugtasks(self.bugtasks)
88 self.assertEqual(expected, search_result)
8989
90 # Private bugs are included in search results for admins.90 # Private bugs are included in search results for admins.
91 admin = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')91 admin = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')
92 params = self.getBugTaskSearchParams(user=admin)92 params = self.getBugTaskSearchParams(user=admin)
93 search_result = self.runSearch(params)93 self.assertSearchFinds(params, self.bugtasks)
94 expected = self.resultValuesForBugtasks(self.bugtasks)
95 self.assertEqual(expected, search_result)
9694
97 def test_search_by_bug_reporter(self):95 def test_search_by_bug_reporter(self):
98 # Search results can be limited to bugs filed by a given person.96 # Search results can be limited to bugs filed by a given person.
@@ -100,9 +98,7 @@
100 reporter = bugtask.bug.owner98 reporter = bugtask.bug.owner
101 params = self.getBugTaskSearchParams(99 params = self.getBugTaskSearchParams(
102 user=None, bug_reporter=reporter)100 user=None, bug_reporter=reporter)
103 search_result = self.runSearch(params)101 self.assertSearchFinds(params, [bugtask])
104 expected = self.resultValuesForBugtasks([bugtask])
105 self.assertEqual(expected, search_result)
106102
107 def test_search_by_bug_commenter(self):103 def test_search_by_bug_commenter(self):
108 # Search results can be limited to bugs having a comment from a104 # Search results can be limited to bugs having a comment from a
@@ -118,9 +114,7 @@
118 expected.bug.newMessage(owner=commenter, content='a comment')114 expected.bug.newMessage(owner=commenter, content='a comment')
119 params = self.getBugTaskSearchParams(115 params = self.getBugTaskSearchParams(
120 user=None, bug_commenter=commenter)116 user=None, bug_commenter=commenter)
121 search_result = self.runSearch(params)117 self.assertSearchFinds(params, [expected])
122 expected = self.resultValuesForBugtasks([expected])
123 self.assertEqual(expected, search_result)
124118
125 def test_search_by_person_affected_by_bug(self):119 def test_search_by_person_affected_by_bug(self):
126 # Search results can be limited to bugs which affect a given person.120 # Search results can be limited to bugs which affect a given person.
@@ -130,9 +124,7 @@
130 expected.bug.markUserAffected(affected_user)124 expected.bug.markUserAffected(affected_user)
131 params = self.getBugTaskSearchParams(125 params = self.getBugTaskSearchParams(
132 user=None, affected_user=affected_user)126 user=None, affected_user=affected_user)
133 search_result = self.runSearch(params)127 self.assertSearchFinds(params, [expected])
134 expected = self.resultValuesForBugtasks([expected])
135 self.assertEqual(expected, search_result)
136128
137 def test_search_by_bugtask_assignee(self):129 def test_search_by_bugtask_assignee(self):
138 # Search results can be limited to bugtask assigned to a given130 # Search results can be limited to bugtask assigned to a given
@@ -142,9 +134,7 @@
142 with person_logged_in(assignee):134 with person_logged_in(assignee):
143 expected.transitionToAssignee(assignee)135 expected.transitionToAssignee(assignee)
144 params = self.getBugTaskSearchParams(user=None, assignee=assignee)136 params = self.getBugTaskSearchParams(user=None, assignee=assignee)
145 search_result = self.runSearch(params)137 self.assertSearchFinds(params, [expected])
146 expected = self.resultValuesForBugtasks([expected])
147 self.assertEqual(expected, search_result)
148138
149 def test_search_by_bug_subscriber(self):139 def test_search_by_bug_subscriber(self):
150 # Search results can be limited to bugs to which a given person140 # Search results can be limited to bugs to which a given person
@@ -154,9 +144,69 @@
154 with person_logged_in(subscriber):144 with person_logged_in(subscriber):
155 expected.bug.subscribe(subscriber, subscribed_by=subscriber)145 expected.bug.subscribe(subscriber, subscribed_by=subscriber)
156 params = self.getBugTaskSearchParams(user=None, subscriber=subscriber)146 params = self.getBugTaskSearchParams(user=None, subscriber=subscriber)
157 search_result = self.runSearch(params)147 self.assertSearchFinds(params, [expected])
158 expected = self.resultValuesForBugtasks([expected])148
159 self.assertEqual(expected, search_result)149 def subscribeToTarget(self, subscriber):
150 # Subscribe the given person to the search target.
151 with person_logged_in(subscriber):
152 self.searchtarget.addSubscription(
153 subscriber, subscribed_by=subscriber)
154
155 def _findBugtaskForOtherProduct(self, bugtask, main_product):
156 # Return the bugtask for the product that is not related to the
157 # main bug target.
158 #
159 # The default bugtasks of this test suite are created by
160 # ObjectFactory.makeBugTask() as follows:
161 # - a new bug is created having a new product as the target.
162 # - another bugtask is created for self.searchtarget (or,
163 # when self.searchtarget is a milestone, for the product
164 # of the milestone)
165 # This method returns the bug task for the product that is not
166 # related to the main bug target.
167 bug = bugtask.bug
168 for other_task in bug.bugtasks:
169 other_target = other_task.target
170 if (IProduct.providedBy(other_target)
171 and other_target != main_product):
172 return other_task
173 self.fail(
174 'No bug task found for a product that is not the target of '
175 'the main test bugtask.')
176
177 def findBugtaskForOtherProduct(self, bugtask):
178 # Return the bugtask for the product that is not related to the
179 # main bug target.
180 #
181 # This method must ober overridden for product related tests.
182 return self._findBugtaskForOtherProduct(bugtask, None)
183
184 def test_search_by_structural_subscriber(self):
185 # Search results can be limited to bugs with a bug target to which
186 # a given person has a structural subscription.
187 subscriber = self.factory.makePerson()
188 # If the given person is not subscribed, no bugtasks are returned.
189 params = self.getBugTaskSearchParams(
190 user=None, structural_subscriber=subscriber)
191 self.assertSearchFinds(params, [])
192 # When the person is subscribed, all bugtasks are returned.
193 self.subscribeToTarget(subscriber)
194 params = self.getBugTaskSearchParams(
195 user=None, structural_subscriber=subscriber)
196 self.assertSearchFinds(params, self.bugtasks)
197
198 # Searching for a structural subscriber does not return a bugtask,
199 # if the person is subscribed to another target than the main
200 # bug target.
201 other_subscriber = self.factory.makePerson()
202 other_bugtask = self.findBugtaskForOtherProduct(self.bugtasks[0])
203 other_target = other_bugtask.target
204 with person_logged_in(other_subscriber):
205 other_target.addSubscription(
206 other_subscriber, subscribed_by=other_subscriber)
207 params = self.getBugTaskSearchParams(
208 user=None, structural_subscriber=other_subscriber)
209 self.assertSearchFinds(params, [])
160210
161 def test_search_by_bug_attachment(self):211 def test_search_by_bug_attachment(self):
162 # Search results can be limited to bugs having attachments of212 # Search results can be limited to bugs having attachments of
@@ -171,23 +221,17 @@
171 # We can search for bugs with non-patch attachments...221 # We can search for bugs with non-patch attachments...
172 params = self.getBugTaskSearchParams(222 params = self.getBugTaskSearchParams(
173 user=None, attachmenttype=BugAttachmentType.UNSPECIFIED)223 user=None, attachmenttype=BugAttachmentType.UNSPECIFIED)
174 search_result = self.runSearch(params)224 self.assertSearchFinds(params, self.bugtasks[:1])
175 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
176 self.assertEqual(expected, search_result)
177 # ... for bugs with patches...225 # ... for bugs with patches...
178 params = self.getBugTaskSearchParams(226 params = self.getBugTaskSearchParams(
179 user=None, attachmenttype=BugAttachmentType.PATCH)227 user=None, attachmenttype=BugAttachmentType.PATCH)
180 search_result = self.runSearch(params)228 self.assertSearchFinds(params, self.bugtasks[1:2])
181 expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
182 self.assertEqual(expected, search_result)
183 # and for bugs with patches or attachments229 # and for bugs with patches or attachments
184 params = self.getBugTaskSearchParams(230 params = self.getBugTaskSearchParams(
185 user=None, attachmenttype=any(231 user=None, attachmenttype=any(
186 BugAttachmentType.PATCH,232 BugAttachmentType.PATCH,
187 BugAttachmentType.UNSPECIFIED))233 BugAttachmentType.UNSPECIFIED))
188 search_result = self.runSearch(params)234 self.assertSearchFinds(params, self.bugtasks[:2])
189 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
190 self.assertEqual(expected, search_result)
191235
192 def setUpFullTextSearchTests(self):236 def setUpFullTextSearchTests(self):
193 # Set text fields indexed by Bug.fti, BugTask.fti or237 # Set text fields indexed by Bug.fti, BugTask.fti or
@@ -205,40 +249,30 @@
205 self.setUpFullTextSearchTests()249 self.setUpFullTextSearchTests()
206 params = self.getBugTaskSearchParams(250 params = self.getBugTaskSearchParams(
207 user=None, searchtext='one title')251 user=None, searchtext='one title')
208 search_result = self.runSearch(params)252 self.assertSearchFinds(params, self.bugtasks[:1])
209 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
210 self.assertEqual(expected, search_result)
211 # ... by BugTask.fti ...253 # ... by BugTask.fti ...
212 params = self.getBugTaskSearchParams(254 params = self.getBugTaskSearchParams(
213 user=None, searchtext='two explanation')255 user=None, searchtext='two explanation')
214 search_result = self.runSearch(params)256 self.assertSearchFinds(params, self.bugtasks[1:2])
215 expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
216 self.assertEqual(expected, search_result)
217 # ...and by MessageChunk.fti257 # ...and by MessageChunk.fti
218 params = self.getBugTaskSearchParams(258 params = self.getBugTaskSearchParams(
219 user=None, searchtext='three comment')259 user=None, searchtext='three comment')
220 search_result = self.runSearch(params)260 self.assertSearchFinds(params, self.bugtasks[2:3])
221 expected = self.resultValuesForBugtasks(self.bugtasks[2:3])
222 self.assertEqual(expected, search_result)
223261
224 def test_fast_fulltext_search(self):262 def test_fast_fulltext_search(self):
225 # Fast full text searches find text indexed by Bug.fti...263 # Fast full text searches find text indexed by Bug.fti...
226 self.setUpFullTextSearchTests()264 self.setUpFullTextSearchTests()
227 params = self.getBugTaskSearchParams(265 params = self.getBugTaskSearchParams(
228 user=None, fast_searchtext='one title')266 user=None, fast_searchtext='one title')
229 search_result = self.runSearch(params)267 self.assertSearchFinds(params, self.bugtasks[:1])
230 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
231 self.assertEqual(expected, search_result)
232 # ... but not text indexed by BugTask.fti ...268 # ... but not text indexed by BugTask.fti ...
233 params = self.getBugTaskSearchParams(269 params = self.getBugTaskSearchParams(
234 user=None, fast_searchtext='two explanation')270 user=None, fast_searchtext='two explanation')
235 search_result = self.runSearch(params)271 self.assertSearchFinds(params, [])
236 self.assertEqual([], search_result)
237 # ..or by MessageChunk.fti272 # ..or by MessageChunk.fti
238 params = self.getBugTaskSearchParams(273 params = self.getBugTaskSearchParams(
239 user=None, fast_searchtext='three comment')274 user=None, fast_searchtext='three comment')
240 search_result = self.runSearch(params)275 self.assertSearchFinds(params, [])
241 self.assertEqual([], search_result)
242276
243 def test_has_no_upstream_bugtask(self):277 def test_has_no_upstream_bugtask(self):
244 # Search results can be limited to bugtasks of bugs that do278 # Search results can be limited to bugtasks of bugs that do
@@ -258,13 +292,13 @@
258 IDistributionSourcePackage.providedBy(self.searchtarget)):292 IDistributionSourcePackage.providedBy(self.searchtarget)):
259 if IDistribution.providedBy(self.searchtarget):293 if IDistribution.providedBy(self.searchtarget):
260 bug = self.factory.makeBug(distribution=self.searchtarget)294 bug = self.factory.makeBug(distribution=self.searchtarget)
261 expected = self.resultValuesForBugtasks([bug.default_bugtask])295 expected = [bug.default_bugtask]
262 else:296 else:
263 bug = self.factory.makeBug(297 bug = self.factory.makeBug(
264 distribution=self.searchtarget.distribution)298 distribution=self.searchtarget.distribution)
265 bugtask = self.factory.makeBugTask(299 bugtask = self.factory.makeBugTask(
266 bug=bug, target=self.searchtarget)300 bug=bug, target=self.searchtarget)
267 expected = self.resultValuesForBugtasks([bugtask])301 expected = [bugtask]
268 else:302 else:
269 # Bugs without distribution related bugtasks have always at303 # Bugs without distribution related bugtasks have always at
270 # least one product related bugtask, hence a304 # least one product related bugtask, hence a
@@ -273,23 +307,14 @@
273 expected = []307 expected = []
274 params = self.getBugTaskSearchParams(308 params = self.getBugTaskSearchParams(
275 user=None, has_no_upstream_bugtask=True)309 user=None, has_no_upstream_bugtask=True)
276 search_result = self.runSearch(params)310 self.assertSearchFinds(params, expected)
277 self.assertEqual(expected, search_result)
278311
279 def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):312 def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
280 # Change the status of another bugtask of the same bug to the313 # Change the status of another bugtask of the same bug to the
281 # given status.314 # given status.
282 bug = bugtask.bug315 other_task = self.findBugtaskForOtherProduct(bugtask)
283 for other_task in bug.bugtasks:316 with person_logged_in(other_task.target.owner):
284 other_target = other_task.target317 other_task.transitionToStatus(new_status, other_task.target.owner)
285 if other_task != bugtask and IProduct.providedBy(other_target):
286 with person_logged_in(other_target.owner):
287 other_task.transitionToStatus(
288 new_status, other_target.owner)
289 return
290 self.fail(
291 'No bug task found for a product that is not the target of '
292 'the main test bugtask.')
293318
294 def test_upstream_status(self):319 def test_upstream_status(self):
295 # Search results can be filtered by the status of an upstream320 # Search results can be filtered by the status of an upstream
@@ -299,14 +324,11 @@
299 # with status NEW for the "other" product, hence all bug tasks324 # with status NEW for the "other" product, hence all bug tasks
300 # will be returned in a search for bugs that are open upstream.325 # will be returned in a search for bugs that are open upstream.
301 params = self.getBugTaskSearchParams(user=None, open_upstream=True)326 params = self.getBugTaskSearchParams(user=None, open_upstream=True)
302 search_result = self.runSearch(params)327 self.assertSearchFinds(params, self.bugtasks)
303 expected = self.resultValuesForBugtasks(self.bugtasks)
304 self.assertEqual(expected, search_result)
305 # A search for tasks resolved upstream does not yield any bugtask.328 # A search for tasks resolved upstream does not yield any bugtask.
306 params = self.getBugTaskSearchParams(329 params = self.getBugTaskSearchParams(
307 user=None, resolved_upstream=True)330 user=None, resolved_upstream=True)
308 search_result = self.runSearch(params)331 self.assertSearchFinds(params, [])
309 self.assertEqual([], search_result)
310 # But if we set upstream bug tasks to "fix committed" or "fix332 # But if we set upstream bug tasks to "fix committed" or "fix
311 # released", the related bug tasks for our test target appear in333 # released", the related bug tasks for our test target appear in
312 # the search result.334 # the search result.
@@ -314,14 +336,11 @@
314 self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)336 self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
315 self.changeStatusOfBugTaskForOtherProduct(337 self.changeStatusOfBugTaskForOtherProduct(
316 self.bugtasks[1], BugTaskStatus.FIXRELEASED)338 self.bugtasks[1], BugTaskStatus.FIXRELEASED)
317 search_result = self.runSearch(params)339 self.assertSearchFinds(params, self.bugtasks[:2])
318 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
319 self.assertEqual(expected, search_result)
320 # A search for bug tasks open upstream now returns only one340 # A search for bug tasks open upstream now returns only one
321 # test task.341 # test task.
322 params = self.getBugTaskSearchParams(user=None, open_upstream=True)342 params = self.getBugTaskSearchParams(user=None, open_upstream=True)
323 search_result = self.runSearch(params)343 self.assertSearchFinds(params, self.bugtasks[2:])
324 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
325344
326 def test_tags(self):345 def test_tags(self):
327 # Search results can be limited to bugs having given tags.346 # Search results can be limited to bugs having given tags.
@@ -330,44 +349,31 @@
330 self.bugtasks[1].bug.tags = ['tag1', 'tag3']349 self.bugtasks[1].bug.tags = ['tag1', 'tag3']
331 params = self.getBugTaskSearchParams(350 params = self.getBugTaskSearchParams(
332 user=None, tag=any('tag2', 'tag3'))351 user=None, tag=any('tag2', 'tag3'))
333 search_result = self.runSearch(params)352 self.assertSearchFinds(params, self.bugtasks[:2])
334 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
335 self.assertEqual(expected, search_result)
336353
337 params = self.getBugTaskSearchParams(354 params = self.getBugTaskSearchParams(
338 user=None, tag=all('tag2', 'tag3'))355 user=None, tag=all('tag2', 'tag3'))
339 search_result = self.runSearch(params)356 self.assertSearchFinds(params, [])
340 self.assertEqual([], search_result)
341357
342 params = self.getBugTaskSearchParams(358 params = self.getBugTaskSearchParams(
343 user=None, tag=all('tag1', 'tag3'))359 user=None, tag=all('tag1', 'tag3'))
344 search_result = self.runSearch(params)360 self.assertSearchFinds(params, self.bugtasks[1:2])
345 expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
346 self.assertEqual(expected, search_result)
347361
348 params = self.getBugTaskSearchParams(362 params = self.getBugTaskSearchParams(
349 user=None, tag=all('tag1', '-tag3'))363 user=None, tag=all('tag1', '-tag3'))
350 search_result = self.runSearch(params)364 self.assertSearchFinds(params, self.bugtasks[:1])
351 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
352 self.assertEqual(expected, search_result)
353365
354 params = self.getBugTaskSearchParams(366 params = self.getBugTaskSearchParams(
355 user=None, tag=all('-tag1'))367 user=None, tag=all('-tag1'))
356 search_result = self.runSearch(params)368 self.assertSearchFinds(params, self.bugtasks[2:])
357 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
358 self.assertEqual(expected, search_result)
359369
360 params = self.getBugTaskSearchParams(370 params = self.getBugTaskSearchParams(
361 user=None, tag=all('*'))371 user=None, tag=all('*'))
362 search_result = self.runSearch(params)372 self.assertSearchFinds(params, self.bugtasks[:2])
363 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
364 self.assertEqual(expected, search_result)
365373
366 params = self.getBugTaskSearchParams(374 params = self.getBugTaskSearchParams(
367 user=None, tag=all('-*'))375 user=None, tag=all('-*'))
368 search_result = self.runSearch(params)376 self.assertSearchFinds(params, self.bugtasks[2:])
369 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
370 self.assertEqual(expected, search_result)
371377
372 def test_date_closed(self):378 def test_date_closed(self):
373 # Search results can be filtered by the date_closed time379 # Search results can be filtered by the date_closed time
@@ -379,13 +385,106 @@
379 self.assertTrue(utc_now >= self.bugtasks[2].date_closed)385 self.assertTrue(utc_now >= self.bugtasks[2].date_closed)
380 params = self.getBugTaskSearchParams(386 params = self.getBugTaskSearchParams(
381 user=None, date_closed=greater_than(utc_now-timedelta(days=1)))387 user=None, date_closed=greater_than(utc_now-timedelta(days=1)))
382 search_result = self.runSearch(params)388 self.assertSearchFinds(params, self.bugtasks[2:])
383 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
384 self.assertEqual(expected, search_result)
385 params = self.getBugTaskSearchParams(389 params = self.getBugTaskSearchParams(
386 user=None, date_closed=greater_than(utc_now+timedelta(days=1)))390 user=None, date_closed=greater_than(utc_now+timedelta(days=1)))
387 search_result = self.runSearch(params)391 self.assertSearchFinds(params, [])
388 self.assertEqual([], search_result)392
393 def test_created_since(self):
394 # Search results can be limited to bugtasks created after a
395 # given time.
396 one_day_ago = self.bugtasks[0].datecreated - timedelta(days=1)
397 two_days_ago = self.bugtasks[0].datecreated - timedelta(days=2)
398 with person_logged_in(self.owner):
399 self.bugtasks[0].datecreated = two_days_ago
400 params = self.getBugTaskSearchParams(
401 user=None, created_since=one_day_ago)
402 self.assertSearchFinds(params, self.bugtasks[1:])
403
404 def test_modified_since(self):
405 # Search results can be limited to bugs modified after a
406 # given time.
407 one_day_ago = (
408 self.bugtasks[0].bug.date_last_updated - timedelta(days=1))
409 two_days_ago = (
410 self.bugtasks[0].bug.date_last_updated - timedelta(days=2))
411 with person_logged_in(self.owner):
412 self.bugtasks[0].bug.date_last_updated = two_days_ago
413 params = self.getBugTaskSearchParams(
414 user=None, modified_since=one_day_ago)
415 self.assertSearchFinds(params, self.bugtasks[1:])
416
417 def test_branches_linked(self):
418 # Search results can be limited to bugs with or without linked
419 # branches.
420 with person_logged_in(self.owner):
421 branch = self.factory.makeBranch()
422 self.bugtasks[0].bug.linkBranch(branch, self.owner)
423 params = self.getBugTaskSearchParams(
424 user=None, linked_branches=BugBranchSearch.BUGS_WITH_BRANCHES)
425 self.assertSearchFinds(params, self.bugtasks[:1])
426 params = self.getBugTaskSearchParams(
427 user=None, linked_branches=BugBranchSearch.BUGS_WITHOUT_BRANCHES)
428 self.assertSearchFinds(params, self.bugtasks[1:])
429
430 def test_limit_search_to_one_bug(self):
431 # Search results can be limited to a given bug.
432 params = self.getBugTaskSearchParams(
433 user=None, bug=self.bugtasks[0].bug)
434 self.assertSearchFinds(params, self.bugtasks[:1])
435 other_bug = self.factory.makeBug()
436 params = self.getBugTaskSearchParams(user=None, bug=other_bug)
437 self.assertSearchFinds(params, [])
438
439 def test_filter_by_status(self):
440 # Search results can be limited to bug tasks with a given status.
441 params = self.getBugTaskSearchParams(
442 user=None, status=BugTaskStatus.FIXCOMMITTED)
443 self.assertSearchFinds(params, self.bugtasks[2:])
444 params = self.getBugTaskSearchParams(
445 user=None, status=any(BugTaskStatus.NEW, BugTaskStatus.TRIAGED))
446 self.assertSearchFinds(params, self.bugtasks[:2])
447 params = self.getBugTaskSearchParams(
448 user=None, status=BugTaskStatus.WONTFIX)
449 self.assertSearchFinds(params, [])
450
451 def test_filter_by_importance(self):
452 # Search results can be limited to bug tasks with a given importance.
453 params = self.getBugTaskSearchParams(
454 user=None, importance=BugTaskImportance.HIGH)
455 self.assertSearchFinds(params, self.bugtasks[:1])
456 params = self.getBugTaskSearchParams(
457 user=None,
458 importance=any(BugTaskImportance.HIGH, BugTaskImportance.LOW))
459 self.assertSearchFinds(params, self.bugtasks[:2])
460 params = self.getBugTaskSearchParams(
461 user=None, importance=BugTaskImportance.MEDIUM)
462 self.assertSearchFinds(params, [])
463
464 def test_omit_duplicate_bugs(self):
465 # Duplicate bugs can optionally be excluded from search results.
466 # The default behaviour is to include duplicates.
467 duplicate_bug = self.bugtasks[0].bug
468 master_bug = self.bugtasks[1].bug
469 with person_logged_in(self.owner):
470 duplicate_bug.markAsDuplicate(master_bug)
471 params = self.getBugTaskSearchParams(user=None)
472 self.assertSearchFinds(params, self.bugtasks)
473 # If we explicitly pass the parameter omit_duplicates=False, we get
474 # the same result.
475 params = self.getBugTaskSearchParams(user=None, omit_dupes=False)
476 self.assertSearchFinds(params, self.bugtasks)
477 # If omit_duplicates is set to True, the first task bug is omitted.
478 params = self.getBugTaskSearchParams(user=None, omit_dupes=True)
479 self.assertSearchFinds(params, self.bugtasks[1:])
480
481 def test_has_cve(self):
482 # Search results can be limited to bugs linked to a CVE.
483 with person_logged_in(self.owner):
484 cve = self.factory.makeCVE('2010-0123')
485 self.bugtasks[0].bug.linkCVE(cve, self.owner)
486 params = self.getBugTaskSearchParams(user=None, has_cve=True)
487 self.assertSearchFinds(params, self.bugtasks[:1])
389488
390489
391class ProductAndDistributionTests:490class ProductAndDistributionTests:
@@ -405,9 +504,7 @@
405 self.bugtasks[0].bug.addNomination(nominator, series1)504 self.bugtasks[0].bug.addNomination(nominator, series1)
406 self.bugtasks[1].bug.addNomination(nominator, series2)505 self.bugtasks[1].bug.addNomination(nominator, series2)
407 params = self.getBugTaskSearchParams(user=None, nominated_for=series1)506 params = self.getBugTaskSearchParams(user=None, nominated_for=series1)
408 search_result = self.runSearch(params)507 self.assertSearchFinds(params, self.bugtasks[:1])
409 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
410 self.assertEqual(expected, search_result)
411508
412509
413class BugTargetTestBase:510class BugTargetTestBase:
@@ -445,15 +542,12 @@
445 supervisor = self.factory.makeTeam(owner=self.owner)542 supervisor = self.factory.makeTeam(owner=self.owner)
446 params = self.getBugTaskSearchParams(543 params = self.getBugTaskSearchParams(
447 user=None, bug_supervisor=supervisor)544 user=None, bug_supervisor=supervisor)
448 search_result = self.runSearch(params)545 self.assertSearchFinds(params, [])
449 self.assertEqual([], search_result)
450546
451 # If we appoint a bug supervisor, searching for bug tasks547 # If we appoint a bug supervisor, searching for bug tasks
452 # by supervisor will return all bugs for our test target.548 # by supervisor will return all bugs for our test target.
453 self.setSupervisor(supervisor)549 self.setSupervisor(supervisor)
454 search_result = self.runSearch(params)550 self.assertSearchFinds(params, self.bugtasks)
455 expected = self.resultValuesForBugtasks(self.bugtasks)
456 self.assertEqual(expected, search_result)
457551
458 def setSupervisor(self, supervisor):552 def setSupervisor(self, supervisor):
459 """Set the bug supervisor for the bug task target."""553 """Set the bug supervisor for the bug task target."""
@@ -484,6 +578,11 @@
484 """See `ProductAndDistributionTests`."""578 """See `ProductAndDistributionTests`."""
485 return self.factory.makeProductSeries(product=self.searchtarget)579 return self.factory.makeProductSeries(product=self.searchtarget)
486580
581 def findBugtaskForOtherProduct(self, bugtask):
582 # Return the bugtask for the product that is not related to the
583 # main bug target.
584 return self._findBugtaskForOtherProduct(bugtask, self.searchtarget)
585
487586
488class ProductSeriesTarget(BugTargetTestBase):587class ProductSeriesTarget(BugTargetTestBase):
489 """Use a product series as the bug target."""588 """Use a product series as the bug target."""
@@ -503,6 +602,31 @@
503 params.setProductSeries(self.searchtarget)602 params.setProductSeries(self.searchtarget)
504 return params603 return params
505604
605 def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
606 # Change the status of another bugtask of the same bug to the
607 # given status.
608 #
609 # This method is called by SearchTestBase.test_upstream_status().
610 # A search for bugs which are open or closed upstream has an
611 # odd behaviour when the search target is a product series: In
612 # this case, all bugs with an open or closed bug task for _any_
613 # product are returned, including bug tasks for the main product
614 # of the series. Hence we must set the status for all products
615 # in order to avoid a failure of test_upstream_status().
616 bug = bugtask.bug
617 for other_task in bugtask.related_tasks:
618 other_target = other_task.target
619 if IProduct.providedBy(other_target):
620 with person_logged_in(other_target.owner):
621 other_task.transitionToStatus(
622 new_status, other_target.owner)
623
624 def findBugtaskForOtherProduct(self, bugtask):
625 # Return the bugtask for the product that not related to the
626 # main bug target.
627 return self._findBugtaskForOtherProduct(
628 bugtask, self.searchtarget.product)
629
506630
507class ProjectGroupTarget(BugTargetTestBase, BugTargetWithBugSuperVisor):631class ProjectGroupTarget(BugTargetTestBase, BugTargetWithBugSuperVisor):
508 """Use a project group as the bug target."""632 """Use a project group as the bug target."""
@@ -525,8 +649,10 @@
525 def makeBugTasks(self):649 def makeBugTasks(self):
526 """Create bug tasks for the search target."""650 """Create bug tasks for the search target."""
527 self.bugtasks = []651 self.bugtasks = []
652 self.products = []
528 with person_logged_in(self.owner):653 with person_logged_in(self.owner):
529 product = self.factory.makeProduct(owner=self.owner)654 product = self.factory.makeProduct(owner=self.owner)
655 self.products.append(product)
530 product.project = self.searchtarget656 product.project = self.searchtarget
531 self.bugtasks.append(657 self.bugtasks.append(
532 self.factory.makeBugTask(target=product))658 self.factory.makeBugTask(target=product))
@@ -535,6 +661,7 @@
535 BugTaskStatus.TRIAGED, self.owner)661 BugTaskStatus.TRIAGED, self.owner)
536662
537 product = self.factory.makeProduct(owner=self.owner)663 product = self.factory.makeProduct(owner=self.owner)
664 self.products.append(product)
538 product.project = self.searchtarget665 product.project = self.searchtarget
539 self.bugtasks.append(666 self.bugtasks.append(
540 self.factory.makeBugTask(target=product))667 self.factory.makeBugTask(target=product))
@@ -543,6 +670,7 @@
543 BugTaskStatus.NEW, self.owner)670 BugTaskStatus.NEW, self.owner)
544671
545 product = self.factory.makeProduct(owner=self.owner)672 product = self.factory.makeProduct(owner=self.owner)
673 self.products.append(product)
546 product.project = self.searchtarget674 product.project = self.searchtarget
547 self.bugtasks.append(675 self.bugtasks.append(
548 self.factory.makeBugTask(target=product))676 self.factory.makeBugTask(target=product))
@@ -557,6 +685,19 @@
557 for bugtask in self.bugtasks:685 for bugtask in self.bugtasks:
558 bugtask.target.setBugSupervisor(supervisor, self.owner)686 bugtask.target.setBugSupervisor(supervisor, self.owner)
559687
688 def findBugtaskForOtherProduct(self, bugtask):
689 # Return the bugtask for the product that not related to the
690 # main bug target.
691 bug = bugtask.bug
692 for other_task in bug.bugtasks:
693 other_target = other_task.target
694 if (IProduct.providedBy(other_target)
695 and other_target not in self.products):
696 return other_task
697 self.fail(
698 'No bug task found for a product that is not the target of '
699 'the main test bugtask.')
700
560701
561class MilestoneTarget(BugTargetTestBase):702class MilestoneTarget(BugTargetTestBase):
562 """Use a milestone as the bug target."""703 """Use a milestone as the bug target."""
@@ -583,6 +724,11 @@
583 for bugtask in self.bugtasks:724 for bugtask in self.bugtasks:
584 bugtask.transitionToMilestone(self.searchtarget, self.owner)725 bugtask.transitionToMilestone(self.searchtarget, self.owner)
585726
727 def findBugtaskForOtherProduct(self, bugtask):
728 # Return the bugtask for the product that not related to the
729 # main bug target.
730 return self._findBugtaskForOtherProduct(bugtask, self.product)
731
586732
587class DistributionTarget(BugTargetTestBase, ProductAndDistributionTests,733class DistributionTarget(BugTargetTestBase, ProductAndDistributionTests,
588 BugTargetWithBugSuperVisor):734 BugTargetWithBugSuperVisor):
@@ -645,6 +791,14 @@
645 params.setSourcePackage(self.searchtarget)791 params.setSourcePackage(self.searchtarget)
646 return params792 return params
647793
794 def subscribeToTarget(self, subscriber):
795 # Subscribe the given person to the search target.
796 # Source packages do not support structural subscriptions,
797 # so we subscribe to the distro series instead.
798 with person_logged_in(subscriber):
799 self.searchtarget.distroseries.addSubscription(
800 subscriber, subscribed_by=subscriber)
801
648802
649class DistributionSourcePackageTarget(BugTargetTestBase,803class DistributionSourcePackageTarget(BugTargetTestBase,
650 BugTargetWithBugSuperVisor):804 BugTargetWithBugSuperVisor):
651805
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-11-03 23:44:56 +0000
+++ lib/lp/testing/factory.py 2010-11-04 15:56:18 +0000
@@ -111,6 +111,10 @@
111 IBugTrackerSet,111 IBugTrackerSet,
112 )112 )
113from lp.bugs.interfaces.bugwatch import IBugWatchSet113from lp.bugs.interfaces.bugwatch import IBugWatchSet
114from lp.bugs.interfaces.cve import (
115 CveStatus,
116 ICveSet,
117 )
114from lp.buildmaster.enums import (118from lp.buildmaster.enums import (
115 BuildFarmJobType,119 BuildFarmJobType,
116 BuildStatus,120 BuildStatus,
@@ -3230,6 +3234,13 @@
3230 consumer, reviewed_by=owner, access_level=access_level)3234 consumer, reviewed_by=owner, access_level=access_level)
3231 return request_token.createAccessToken()3235 return request_token.createAccessToken()
32323236
3237 def makeCVE(self, sequence, description=None,
3238 cvestate=CveStatus.CANDIDATE):
3239 """Create a new CVE record."""
3240 if description is None:
3241 description = self.getUniqueString()
3242 return getUtility(ICveSet).new(sequence, description, cvestate)
3243
32333244
3234# Some factory methods return simple Python types. We don't add3245# Some factory methods return simple Python types. We don't add
3235# security wrappers for them, as well as for objects created by3246# security wrappers for them, as well as for objects created by
32363247
=== modified file 'lib/lp/testing/tests/test_factory.py'
--- lib/lp/testing/tests/test_factory.py 2010-10-25 19:11:46 +0000
+++ lib/lp/testing/tests/test_factory.py 2010-11-04 15:56:18 +0000
@@ -17,6 +17,10 @@
17 DatabaseFunctionalLayer,17 DatabaseFunctionalLayer,
18 LaunchpadZopelessLayer,18 LaunchpadZopelessLayer,
19 )19 )
20from lp.bugs.interfaces.cve import (
21 CveStatus,
22 ICve,
23 )
20from lp.buildmaster.enums import BuildStatus24from lp.buildmaster.enums import BuildStatus
21from lp.code.enums import (25from lp.code.enums import (
22 BranchType,26 BranchType,
@@ -492,6 +496,24 @@
492 ssp = self.factory.makeSuiteSourcePackage()496 ssp = self.factory.makeSuiteSourcePackage()
493 self.assertThat(ssp, ProvidesAndIsProxied(ISuiteSourcePackage))497 self.assertThat(ssp, ProvidesAndIsProxied(ISuiteSourcePackage))
494498
499 # makeCVE
500 def test_makeCVE_returns_cve(self):
501 cve = self.factory.makeCVE(sequence='2000-1234')
502 self.assertThat(cve, ProvidesAndIsProxied(ICve))
503
504 def test_makeCVE_uses_sequence(self):
505 cve = self.factory.makeCVE(sequence='2000-1234')
506 self.assertEqual('2000-1234', cve.sequence)
507
508 def test_makeCVE_uses_description(self):
509 cve = self.factory.makeCVE(sequence='2000-1234', description='foo')
510 self.assertEqual('foo', cve.description)
511
512 def test_makeCVE_uses_cve_status(self):
513 cve = self.factory.makeCVE(
514 sequence='2000-1234', cvestate=CveStatus.DEPRECATED)
515 self.assertEqual(CveStatus.DEPRECATED, cve.status)
516
495517
496class TestFactoryWithLibrarian(TestCaseWithFactory):518class TestFactoryWithLibrarian(TestCaseWithFactory):
497519