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

Proposed by Abel Deuring
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 11843
Proposed branch: lp:~adeuring/launchpad/bug-594247-unittests-for-searchtasks-3
Merge into: lp:launchpad
Diff against target: 310 lines (+230/-8)
2 files modified
lib/lp/bugs/tests/test_bugtask_search.py (+218/-3)
lib/lp/testing/factory.py (+12/-5)
To merge this branch: bzr merge lp:~adeuring/launchpad/bug-594247-unittests-for-searchtasks-3
Reviewer Review Type Date Requested Status
Gavin Panella (community) Approve
Review via email: mp+39523@code.launchpad.net

Commit message

unit tests for BugTaskSet.search(): upstream status related filtering, filtering by tags, by date_closed, by fulltext search, by fast fullext search and by has_no_upstream_bugtask

Description of the change

This branch adds more unit tests for BugTaskSet.search().

I modified the testing factory method makeBug(): It is now possible to pass a distribution as the bug target. Without this change, tests of bug tasks for bugs without an upstream bugtask would have been somehwat pointless: Without this change, any bug created via makeBug() or makeBugTask() has at least one bugtask targeted to a product. In other words, a search for bugtasks without a related upstream task would never return anything.

test: ./bin/test -vvt lp.bugs.tests.test_bugtask_search

no lint

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

Cool. Only one very trivial comment.

[1]

+ assert(bug.default_bugtask.target == bugtarget)

This works, but it's misleading because assert is a statement not a
function. Actually... did you mean to remove it before landing? If
not, perhaps it should be changed to an assertEqual() call.

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

Hi Gavin,

thanks for the review!

On 28.10.2010 15:27, Gavin Panella wrote:
> Review: Approve
> Cool. Only one very trivial comment.
>
>
> [1]
>
> + assert(bug.default_bugtask.target == bugtarget)
>
> This works, but it's misleading because assert is a statement not a
> function. Actually... did you mean to remove it before landing? If
> not, perhaps it should be changed to an assertEqual() call.

yeah, sure this is just a relic from debugging. I was a bit puzzled to
see it in the diff -- I could not find it in my local version. Turns out
that I forgot to "bzr commit" my latest changes...

Revision history for this message
Gavin Panella (allenap) wrote :

Re-reviewing.

review: Abstain
Revision history for this message
Gavin Panella (allenap) wrote :

Hi Abel, I decided to start from scratch!

[1]

+from lp.registry.interfaces.sourcepackage import ISourcePackage
+from lp.registry.interfaces.distributionsourcepackage import (

These are out of order. Consider using utilities/format-import (or
it's more convenient brother format-new-and-modified-imports).

[2]

+ search_result = self.runSearch(params)
+ expected = self.resultValuesForBugtasks(self.bugtasks[2:3])
+ self.assertEqual(expected, search_result)

It's not worth changing it here, but a custom assertion method might
have saved a few keystrokes:

    def assertSearchFinds(self, params, bugtasks):
        search_result = self.runSearch(params)
        expected = self.resultValuesForBugtasks(bugtasks)
        self.assertEqual(expected, search_result)

[3]

+ raise AssertionError(
+ 'No bug task found for a product that is not the target of '
+ 'the main test bugtask.')

Either raise self.failureException or call self.fail(message).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py 2010-10-22 13:25:22 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py 2010-10-29 13:04:06 +0000
@@ -3,13 +3,22 @@
33
4__metaclass__ = type4__metaclass__ = type
55
6from datetime import (
7 datetime,
8 timedelta,
9 )
6from new import classobj10from new import classobj
11import pytz
7import sys12import sys
8import unittest13import unittest
914
10from zope.component import getUtility15from zope.component import getUtility
1116
12from canonical.launchpad.searchbuilder import any17from canonical.launchpad.searchbuilder import (
18 all,
19 any,
20 greater_than,
21 )
13from canonical.testing.layers import (22from canonical.testing.layers import (
14 LaunchpadFunctionalLayer,23 LaunchpadFunctionalLayer,
15 )24 )
@@ -21,7 +30,14 @@
21 BugTaskStatus,30 BugTaskStatus,
22 IBugTaskSet,31 IBugTaskSet,
23 )32 )
33from lp.registry.interfaces.distribution import IDistribution
34from lp.registry.interfaces.distributionsourcepackage import (
35 IDistributionSourcePackage,
36 )
37from lp.registry.interfaces.distroseries import IDistroSeries
24from lp.registry.interfaces.person import IPersonSet38from lp.registry.interfaces.person import IPersonSet
39from lp.registry.interfaces.product import IProduct
40from lp.registry.interfaces.sourcepackage import ISourcePackage
25from lp.testing import (41from lp.testing import (
26 person_logged_in,42 person_logged_in,
27 TestCaseWithFactory,43 TestCaseWithFactory,
@@ -173,6 +189,204 @@
173 expected = self.resultValuesForBugtasks(self.bugtasks[:2])189 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
174 self.assertEqual(expected, search_result)190 self.assertEqual(expected, search_result)
175191
192 def setUpFullTextSearchTests(self):
193 # Set text fields indexed by Bug.fti, BugTask.fti or
194 # MessageChunk.fti to values we can search for.
195 for bugtask, number in zip(self.bugtasks, ('one', 'two', 'three')):
196 commenter = self.bugtasks[0].bug.owner
197 with person_logged_in(commenter):
198 bugtask.statusexplanation = 'status explanation %s' % number
199 bugtask.bug.title = 'bug title %s' % number
200 bugtask.bug.newMessage(
201 owner=commenter, content='comment %s' % number)
202
203 def test_fulltext_search(self):
204 # Full text searches find text indexed by Bug.fti...
205 self.setUpFullTextSearchTests()
206 params = self.getBugTaskSearchParams(
207 user=None, searchtext='one title')
208 search_result = self.runSearch(params)
209 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
210 self.assertEqual(expected, search_result)
211 # ... by BugTask.fti ...
212 params = self.getBugTaskSearchParams(
213 user=None, searchtext='two explanation')
214 search_result = self.runSearch(params)
215 expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
216 self.assertEqual(expected, search_result)
217 # ...and by MessageChunk.fti
218 params = self.getBugTaskSearchParams(
219 user=None, searchtext='three comment')
220 search_result = self.runSearch(params)
221 expected = self.resultValuesForBugtasks(self.bugtasks[2:3])
222 self.assertEqual(expected, search_result)
223
224 def test_fast_fulltext_search(self):
225 # Fast full text searches find text indexed by Bug.fti...
226 self.setUpFullTextSearchTests()
227 params = self.getBugTaskSearchParams(
228 user=None, fast_searchtext='one title')
229 search_result = self.runSearch(params)
230 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
231 self.assertEqual(expected, search_result)
232 # ... but not text indexed by BugTask.fti ...
233 params = self.getBugTaskSearchParams(
234 user=None, fast_searchtext='two explanation')
235 search_result = self.runSearch(params)
236 self.assertEqual([], search_result)
237 # ..or by MessageChunk.fti
238 params = self.getBugTaskSearchParams(
239 user=None, fast_searchtext='three comment')
240 search_result = self.runSearch(params)
241 self.assertEqual([], search_result)
242
243 def test_has_no_upstream_bugtask(self):
244 # Search results can be limited to bugtasks of bugs that do
245 # not have a related upstream task.
246 #
247 # All bugs created in makeBugTasks() have at least one
248 # bug task for a product: The default bug task created
249 # by lp.testing.factory.Factory.makeBug() if neither a
250 # product nor a distribution is specified. For distribution
251 # related tests we need another bug which does not have
252 # an upstream (aka product) bug task, otherwise the set of
253 # bugtasks returned for a search for has_no_upstream_bugtask
254 # would always be empty.
255 if (IDistribution.providedBy(self.searchtarget) or
256 IDistroSeries.providedBy(self.searchtarget) or
257 ISourcePackage.providedBy(self.searchtarget) or
258 IDistributionSourcePackage.providedBy(self.searchtarget)):
259 if IDistribution.providedBy(self.searchtarget):
260 bug = self.factory.makeBug(distribution=self.searchtarget)
261 expected = self.resultValuesForBugtasks([bug.default_bugtask])
262 else:
263 bug = self.factory.makeBug(
264 distribution=self.searchtarget.distribution)
265 bugtask = self.factory.makeBugTask(
266 bug=bug, target=self.searchtarget)
267 expected = self.resultValuesForBugtasks([bugtask])
268 else:
269 # Bugs without distribution related bugtasks have always at
270 # least one product related bugtask, hence a
271 # has_no_upstream_bugtask search will always return an
272 # empty result set.
273 expected = []
274 params = self.getBugTaskSearchParams(
275 user=None, has_no_upstream_bugtask=True)
276 search_result = self.runSearch(params)
277 self.assertEqual(expected, search_result)
278
279 def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
280 # Change the status of another bugtask of the same bug to the
281 # given status.
282 bug = bugtask.bug
283 for other_task in bug.bugtasks:
284 other_target = other_task.target
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.')
293
294 def test_upstream_status(self):
295 # Search results can be filtered by the status of an upstream
296 # bug task.
297 #
298 # The bug task status of the default test data has only bug tasks
299 # with status NEW for the "other" product, hence all bug tasks
300 # will be returned in a search for bugs that are open upstream.
301 params = self.getBugTaskSearchParams(user=None, open_upstream=True)
302 search_result = self.runSearch(params)
303 expected = self.resultValuesForBugtasks(self.bugtasks)
304 self.assertEqual(expected, search_result)
305 # A search for tasks resolved upstream does not yield any bugtask.
306 params = self.getBugTaskSearchParams(
307 user=None, resolved_upstream=True)
308 search_result = self.runSearch(params)
309 self.assertEqual([], search_result)
310 # But if we set upstream bug tasks to "fix committed" or "fix
311 # released", the related bug tasks for our test target appear in
312 # the search result.
313 self.changeStatusOfBugTaskForOtherProduct(
314 self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
315 self.changeStatusOfBugTaskForOtherProduct(
316 self.bugtasks[1], BugTaskStatus.FIXRELEASED)
317 search_result = self.runSearch(params)
318 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
319 self.assertEqual(expected, search_result)
320 # A search for bug tasks open upstream now returns only one
321 # test task.
322 params = self.getBugTaskSearchParams(user=None, open_upstream=True)
323 search_result = self.runSearch(params)
324 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
325
326 def test_tags(self):
327 # Search results can be limited to bugs having given tags.
328 with person_logged_in(self.owner):
329 self.bugtasks[0].bug.tags = ['tag1', 'tag2']
330 self.bugtasks[1].bug.tags = ['tag1', 'tag3']
331 params = self.getBugTaskSearchParams(
332 user=None, tag=any('tag2', 'tag3'))
333 search_result = self.runSearch(params)
334 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
335 self.assertEqual(expected, search_result)
336
337 params = self.getBugTaskSearchParams(
338 user=None, tag=all('tag2', 'tag3'))
339 search_result = self.runSearch(params)
340 self.assertEqual([], search_result)
341
342 params = self.getBugTaskSearchParams(
343 user=None, tag=all('tag1', 'tag3'))
344 search_result = self.runSearch(params)
345 expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
346 self.assertEqual(expected, search_result)
347
348 params = self.getBugTaskSearchParams(
349 user=None, tag=all('tag1', '-tag3'))
350 search_result = self.runSearch(params)
351 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
352 self.assertEqual(expected, search_result)
353
354 params = self.getBugTaskSearchParams(
355 user=None, tag=all('-tag1'))
356 search_result = self.runSearch(params)
357 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
358 self.assertEqual(expected, search_result)
359
360 params = self.getBugTaskSearchParams(
361 user=None, tag=all('*'))
362 search_result = self.runSearch(params)
363 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
364 self.assertEqual(expected, search_result)
365
366 params = self.getBugTaskSearchParams(
367 user=None, tag=all('-*'))
368 search_result = self.runSearch(params)
369 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
370 self.assertEqual(expected, search_result)
371
372 def test_date_closed(self):
373 # Search results can be filtered by the date_closed time
374 # of a bugtask.
375 with person_logged_in(self.owner):
376 self.bugtasks[2].transitionToStatus(
377 BugTaskStatus.FIXRELEASED, self.owner)
378 utc_now = datetime.now(pytz.timezone('UTC'))
379 self.assertTrue(utc_now >= self.bugtasks[2].date_closed)
380 params = self.getBugTaskSearchParams(
381 user=None, date_closed=greater_than(utc_now-timedelta(days=1)))
382 search_result = self.runSearch(params)
383 expected = self.resultValuesForBugtasks(self.bugtasks[2:])
384 self.assertEqual(expected, search_result)
385 params = self.getBugTaskSearchParams(
386 user=None, date_closed=greater_than(utc_now+timedelta(days=1)))
387 search_result = self.runSearch(params)
388 self.assertEqual([], search_result)
389
176390
177class ProductAndDistributionTests:391class ProductAndDistributionTests:
178 """Tests which are useful for distributions and products."""392 """Tests which are useful for distributions and products."""
@@ -190,8 +404,7 @@
190 with person_logged_in(self.owner):404 with person_logged_in(self.owner):
191 self.bugtasks[0].bug.addNomination(nominator, series1)405 self.bugtasks[0].bug.addNomination(nominator, series1)
192 self.bugtasks[1].bug.addNomination(nominator, series2)406 self.bugtasks[1].bug.addNomination(nominator, series2)
193 params = self.getBugTaskSearchParams(407 params = self.getBugTaskSearchParams(user=None, nominated_for=series1)
194 user=None, nominated_for=series1)
195 search_result = self.runSearch(params)408 search_result = self.runSearch(params)
196 expected = self.resultValuesForBugtasks(self.bugtasks[:1])409 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
197 self.assertEqual(expected, search_result)410 self.assertEqual(expected, search_result)
@@ -323,6 +536,8 @@
323536
324 product = self.factory.makeProduct(owner=self.owner)537 product = self.factory.makeProduct(owner=self.owner)
325 product.project = self.searchtarget538 product.project = self.searchtarget
539 self.bugtasks.append(
540 self.factory.makeBugTask(target=product))
326 self.bugtasks[-1].importance = BugTaskImportance.LOW541 self.bugtasks[-1].importance = BugTaskImportance.LOW
327 self.bugtasks[-1].transitionToStatus(542 self.bugtasks[-1].transitionToStatus(
328 BugTaskStatus.NEW, self.owner)543 BugTaskStatus.NEW, self.owner)
329544
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-10-27 22:33:01 +0000
+++ lib/lp/testing/factory.py 2010-10-29 13:04:06 +0000
@@ -1333,19 +1333,25 @@
1333 def makeBug(self, product=None, owner=None, bug_watch_url=None,1333 def makeBug(self, product=None, owner=None, bug_watch_url=None,
1334 private=False, date_closed=None, title=None,1334 private=False, date_closed=None, title=None,
1335 date_created=None, description=None, comment=None,1335 date_created=None, description=None, comment=None,
1336 status=None):1336 status=None, distribution=None):
1337 """Create and return a new, arbitrary Bug.1337 """Create and return a new, arbitrary Bug.
13381338
1339 The bug returned uses default values where possible. See1339 The bug returned uses default values where possible. See
1340 `IBugSet.new` for more information.1340 `IBugSet.new` for more information.
13411341
1342 :param product: If the product is not set, one is created1342 :param product: If the product is not set, and if the parameter
1343 and this is used as the primary bug target.1343 distribution is not set, a product is created and this is
1344 used as the primary bug target.
1344 :param owner: The reporter of the bug. If not set, one is created.1345 :param owner: The reporter of the bug. If not set, one is created.
1345 :param bug_watch_url: If specified, create a bug watch pointing1346 :param bug_watch_url: If specified, create a bug watch pointing
1346 to this URL.1347 to this URL.
1348 :param distribution: If set, the distribution is used as the
1349 default bug target.
1350
1351 At least one of the parameters distribution and product must be
1352 None, otherwise, an assertion error will be raised.
1347 """1353 """
1348 if product is None:1354 if product is None and distribution is None:
1349 product = self.makeProduct()1355 product = self.makeProduct()
1350 if owner is None:1356 if owner is None:
1351 owner = self.makePerson()1357 owner = self.makePerson()
@@ -1357,7 +1363,8 @@
1357 owner, title, comment=comment, private=private,1363 owner, title, comment=comment, private=private,
1358 datecreated=date_created, description=description,1364 datecreated=date_created, description=description,
1359 status=status)1365 status=status)
1360 create_bug_params.setBugTarget(product=product)1366 create_bug_params.setBugTarget(
1367 product=product, distribution=distribution)
1361 bug = getUtility(IBugSet).createBug(create_bug_params)1368 bug = getUtility(IBugSet).createBug(create_bug_params)
1362 if bug_watch_url is not None:1369 if bug_watch_url is not None:
1363 # fromText() creates a bug watch associated with the bug.1370 # fromText() creates a bug watch associated with the bug.