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
1=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
2--- lib/lp/bugs/tests/test_bugtask_search.py 2010-10-22 13:25:22 +0000
3+++ lib/lp/bugs/tests/test_bugtask_search.py 2010-10-29 13:04:06 +0000
4@@ -3,13 +3,22 @@
5
6 __metaclass__ = type
7
8+from datetime import (
9+ datetime,
10+ timedelta,
11+ )
12 from new import classobj
13+import pytz
14 import sys
15 import unittest
16
17 from zope.component import getUtility
18
19-from canonical.launchpad.searchbuilder import any
20+from canonical.launchpad.searchbuilder import (
21+ all,
22+ any,
23+ greater_than,
24+ )
25 from canonical.testing.layers import (
26 LaunchpadFunctionalLayer,
27 )
28@@ -21,7 +30,14 @@
29 BugTaskStatus,
30 IBugTaskSet,
31 )
32+from lp.registry.interfaces.distribution import IDistribution
33+from lp.registry.interfaces.distributionsourcepackage import (
34+ IDistributionSourcePackage,
35+ )
36+from lp.registry.interfaces.distroseries import IDistroSeries
37 from lp.registry.interfaces.person import IPersonSet
38+from lp.registry.interfaces.product import IProduct
39+from lp.registry.interfaces.sourcepackage import ISourcePackage
40 from lp.testing import (
41 person_logged_in,
42 TestCaseWithFactory,
43@@ -173,6 +189,204 @@
44 expected = self.resultValuesForBugtasks(self.bugtasks[:2])
45 self.assertEqual(expected, search_result)
46
47+ def setUpFullTextSearchTests(self):
48+ # Set text fields indexed by Bug.fti, BugTask.fti or
49+ # MessageChunk.fti to values we can search for.
50+ for bugtask, number in zip(self.bugtasks, ('one', 'two', 'three')):
51+ commenter = self.bugtasks[0].bug.owner
52+ with person_logged_in(commenter):
53+ bugtask.statusexplanation = 'status explanation %s' % number
54+ bugtask.bug.title = 'bug title %s' % number
55+ bugtask.bug.newMessage(
56+ owner=commenter, content='comment %s' % number)
57+
58+ def test_fulltext_search(self):
59+ # Full text searches find text indexed by Bug.fti...
60+ self.setUpFullTextSearchTests()
61+ params = self.getBugTaskSearchParams(
62+ user=None, searchtext='one title')
63+ search_result = self.runSearch(params)
64+ expected = self.resultValuesForBugtasks(self.bugtasks[:1])
65+ self.assertEqual(expected, search_result)
66+ # ... by BugTask.fti ...
67+ params = self.getBugTaskSearchParams(
68+ user=None, searchtext='two explanation')
69+ search_result = self.runSearch(params)
70+ expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
71+ self.assertEqual(expected, search_result)
72+ # ...and by MessageChunk.fti
73+ params = self.getBugTaskSearchParams(
74+ user=None, searchtext='three comment')
75+ search_result = self.runSearch(params)
76+ expected = self.resultValuesForBugtasks(self.bugtasks[2:3])
77+ self.assertEqual(expected, search_result)
78+
79+ def test_fast_fulltext_search(self):
80+ # Fast full text searches find text indexed by Bug.fti...
81+ self.setUpFullTextSearchTests()
82+ params = self.getBugTaskSearchParams(
83+ user=None, fast_searchtext='one title')
84+ search_result = self.runSearch(params)
85+ expected = self.resultValuesForBugtasks(self.bugtasks[:1])
86+ self.assertEqual(expected, search_result)
87+ # ... but not text indexed by BugTask.fti ...
88+ params = self.getBugTaskSearchParams(
89+ user=None, fast_searchtext='two explanation')
90+ search_result = self.runSearch(params)
91+ self.assertEqual([], search_result)
92+ # ..or by MessageChunk.fti
93+ params = self.getBugTaskSearchParams(
94+ user=None, fast_searchtext='three comment')
95+ search_result = self.runSearch(params)
96+ self.assertEqual([], search_result)
97+
98+ def test_has_no_upstream_bugtask(self):
99+ # Search results can be limited to bugtasks of bugs that do
100+ # not have a related upstream task.
101+ #
102+ # All bugs created in makeBugTasks() have at least one
103+ # bug task for a product: The default bug task created
104+ # by lp.testing.factory.Factory.makeBug() if neither a
105+ # product nor a distribution is specified. For distribution
106+ # related tests we need another bug which does not have
107+ # an upstream (aka product) bug task, otherwise the set of
108+ # bugtasks returned for a search for has_no_upstream_bugtask
109+ # would always be empty.
110+ if (IDistribution.providedBy(self.searchtarget) or
111+ IDistroSeries.providedBy(self.searchtarget) or
112+ ISourcePackage.providedBy(self.searchtarget) or
113+ IDistributionSourcePackage.providedBy(self.searchtarget)):
114+ if IDistribution.providedBy(self.searchtarget):
115+ bug = self.factory.makeBug(distribution=self.searchtarget)
116+ expected = self.resultValuesForBugtasks([bug.default_bugtask])
117+ else:
118+ bug = self.factory.makeBug(
119+ distribution=self.searchtarget.distribution)
120+ bugtask = self.factory.makeBugTask(
121+ bug=bug, target=self.searchtarget)
122+ expected = self.resultValuesForBugtasks([bugtask])
123+ else:
124+ # Bugs without distribution related bugtasks have always at
125+ # least one product related bugtask, hence a
126+ # has_no_upstream_bugtask search will always return an
127+ # empty result set.
128+ expected = []
129+ params = self.getBugTaskSearchParams(
130+ user=None, has_no_upstream_bugtask=True)
131+ search_result = self.runSearch(params)
132+ self.assertEqual(expected, search_result)
133+
134+ def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
135+ # Change the status of another bugtask of the same bug to the
136+ # given status.
137+ bug = bugtask.bug
138+ for other_task in bug.bugtasks:
139+ other_target = other_task.target
140+ if other_task != bugtask and IProduct.providedBy(other_target):
141+ with person_logged_in(other_target.owner):
142+ other_task.transitionToStatus(
143+ new_status, other_target.owner)
144+ return
145+ self.fail(
146+ 'No bug task found for a product that is not the target of '
147+ 'the main test bugtask.')
148+
149+ def test_upstream_status(self):
150+ # Search results can be filtered by the status of an upstream
151+ # bug task.
152+ #
153+ # The bug task status of the default test data has only bug tasks
154+ # with status NEW for the "other" product, hence all bug tasks
155+ # will be returned in a search for bugs that are open upstream.
156+ params = self.getBugTaskSearchParams(user=None, open_upstream=True)
157+ search_result = self.runSearch(params)
158+ expected = self.resultValuesForBugtasks(self.bugtasks)
159+ self.assertEqual(expected, search_result)
160+ # A search for tasks resolved upstream does not yield any bugtask.
161+ params = self.getBugTaskSearchParams(
162+ user=None, resolved_upstream=True)
163+ search_result = self.runSearch(params)
164+ self.assertEqual([], search_result)
165+ # But if we set upstream bug tasks to "fix committed" or "fix
166+ # released", the related bug tasks for our test target appear in
167+ # the search result.
168+ self.changeStatusOfBugTaskForOtherProduct(
169+ self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
170+ self.changeStatusOfBugTaskForOtherProduct(
171+ self.bugtasks[1], BugTaskStatus.FIXRELEASED)
172+ search_result = self.runSearch(params)
173+ expected = self.resultValuesForBugtasks(self.bugtasks[:2])
174+ self.assertEqual(expected, search_result)
175+ # A search for bug tasks open upstream now returns only one
176+ # test task.
177+ params = self.getBugTaskSearchParams(user=None, open_upstream=True)
178+ search_result = self.runSearch(params)
179+ expected = self.resultValuesForBugtasks(self.bugtasks[2:])
180+
181+ def test_tags(self):
182+ # Search results can be limited to bugs having given tags.
183+ with person_logged_in(self.owner):
184+ self.bugtasks[0].bug.tags = ['tag1', 'tag2']
185+ self.bugtasks[1].bug.tags = ['tag1', 'tag3']
186+ params = self.getBugTaskSearchParams(
187+ user=None, tag=any('tag2', 'tag3'))
188+ search_result = self.runSearch(params)
189+ expected = self.resultValuesForBugtasks(self.bugtasks[:2])
190+ self.assertEqual(expected, search_result)
191+
192+ params = self.getBugTaskSearchParams(
193+ user=None, tag=all('tag2', 'tag3'))
194+ search_result = self.runSearch(params)
195+ self.assertEqual([], search_result)
196+
197+ params = self.getBugTaskSearchParams(
198+ user=None, tag=all('tag1', 'tag3'))
199+ search_result = self.runSearch(params)
200+ expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
201+ self.assertEqual(expected, search_result)
202+
203+ params = self.getBugTaskSearchParams(
204+ user=None, tag=all('tag1', '-tag3'))
205+ search_result = self.runSearch(params)
206+ expected = self.resultValuesForBugtasks(self.bugtasks[:1])
207+ self.assertEqual(expected, search_result)
208+
209+ params = self.getBugTaskSearchParams(
210+ user=None, tag=all('-tag1'))
211+ search_result = self.runSearch(params)
212+ expected = self.resultValuesForBugtasks(self.bugtasks[2:])
213+ self.assertEqual(expected, search_result)
214+
215+ params = self.getBugTaskSearchParams(
216+ user=None, tag=all('*'))
217+ search_result = self.runSearch(params)
218+ expected = self.resultValuesForBugtasks(self.bugtasks[:2])
219+ self.assertEqual(expected, search_result)
220+
221+ params = self.getBugTaskSearchParams(
222+ user=None, tag=all('-*'))
223+ search_result = self.runSearch(params)
224+ expected = self.resultValuesForBugtasks(self.bugtasks[2:])
225+ self.assertEqual(expected, search_result)
226+
227+ def test_date_closed(self):
228+ # Search results can be filtered by the date_closed time
229+ # of a bugtask.
230+ with person_logged_in(self.owner):
231+ self.bugtasks[2].transitionToStatus(
232+ BugTaskStatus.FIXRELEASED, self.owner)
233+ utc_now = datetime.now(pytz.timezone('UTC'))
234+ self.assertTrue(utc_now >= self.bugtasks[2].date_closed)
235+ params = self.getBugTaskSearchParams(
236+ user=None, date_closed=greater_than(utc_now-timedelta(days=1)))
237+ search_result = self.runSearch(params)
238+ expected = self.resultValuesForBugtasks(self.bugtasks[2:])
239+ self.assertEqual(expected, search_result)
240+ params = self.getBugTaskSearchParams(
241+ user=None, date_closed=greater_than(utc_now+timedelta(days=1)))
242+ search_result = self.runSearch(params)
243+ self.assertEqual([], search_result)
244+
245
246 class ProductAndDistributionTests:
247 """Tests which are useful for distributions and products."""
248@@ -190,8 +404,7 @@
249 with person_logged_in(self.owner):
250 self.bugtasks[0].bug.addNomination(nominator, series1)
251 self.bugtasks[1].bug.addNomination(nominator, series2)
252- params = self.getBugTaskSearchParams(
253- user=None, nominated_for=series1)
254+ params = self.getBugTaskSearchParams(user=None, nominated_for=series1)
255 search_result = self.runSearch(params)
256 expected = self.resultValuesForBugtasks(self.bugtasks[:1])
257 self.assertEqual(expected, search_result)
258@@ -323,6 +536,8 @@
259
260 product = self.factory.makeProduct(owner=self.owner)
261 product.project = self.searchtarget
262+ self.bugtasks.append(
263+ self.factory.makeBugTask(target=product))
264 self.bugtasks[-1].importance = BugTaskImportance.LOW
265 self.bugtasks[-1].transitionToStatus(
266 BugTaskStatus.NEW, self.owner)
267
268=== modified file 'lib/lp/testing/factory.py'
269--- lib/lp/testing/factory.py 2010-10-27 22:33:01 +0000
270+++ lib/lp/testing/factory.py 2010-10-29 13:04:06 +0000
271@@ -1333,19 +1333,25 @@
272 def makeBug(self, product=None, owner=None, bug_watch_url=None,
273 private=False, date_closed=None, title=None,
274 date_created=None, description=None, comment=None,
275- status=None):
276+ status=None, distribution=None):
277 """Create and return a new, arbitrary Bug.
278
279 The bug returned uses default values where possible. See
280 `IBugSet.new` for more information.
281
282- :param product: If the product is not set, one is created
283- and this is used as the primary bug target.
284+ :param product: If the product is not set, and if the parameter
285+ distribution is not set, a product is created and this is
286+ used as the primary bug target.
287 :param owner: The reporter of the bug. If not set, one is created.
288 :param bug_watch_url: If specified, create a bug watch pointing
289 to this URL.
290+ :param distribution: If set, the distribution is used as the
291+ default bug target.
292+
293+ At least one of the parameters distribution and product must be
294+ None, otherwise, an assertion error will be raised.
295 """
296- if product is None:
297+ if product is None and distribution is None:
298 product = self.makeProduct()
299 if owner is None:
300 owner = self.makePerson()
301@@ -1357,7 +1363,8 @@
302 owner, title, comment=comment, private=private,
303 datecreated=date_created, description=description,
304 status=status)
305- create_bug_params.setBugTarget(product=product)
306+ create_bug_params.setBugTarget(
307+ product=product, distribution=distribution)
308 bug = getUtility(IBugSet).createBug(create_bug_params)
309 if bug_watch_url is not None:
310 # fromText() creates a bug watch associated with the bug.