Merge lp:~wgrant/launchpad/trim-bugtask-search-tests into lp:launchpad

Proposed by William Grant on 2012-04-19
Status: Merged
Approved by: William Grant on 2012-04-19
Approved revision: no longer in the source branch.
Merged at revision: 15122
Proposed branch: lp:~wgrant/launchpad/trim-bugtask-search-tests
Merge into: lp:launchpad
Diff against target: 393 lines (+180/-161)
1 file modified
lib/lp/bugs/tests/test_bugtask_search.py (+180/-161)
To merge this branch: bzr merge lp:~wgrant/launchpad/trim-bugtask-search-tests
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code 2012-04-19 Approve on 2012-04-19
Review via email: mp+102628@code.launchpad.net

Commit Message

Cut test_bugtask_search runtime by >20 minutes without significantly reducing test coverage.

Description of the Change

test_bugtask_search currently multiplies most of its tests across three dimensions: target type (Product, Distribution, etc.), result type (preloaded BugTasks, not preloaded BugTasks, or Bug.id), and implementation (legacy or BugTaskFlat). The last is temporary, but the others are permanent. The tests currently take half an hour to run on modern hardware.

This branch cuts the number of effective tests by nearly 2/3, and most of the eliminated tests are the slow ones (Distribution, DistributionSourcePackage and SourcePackage tests have high setup times). Instead of multiplying all the tests by target, only those which have some change of being target-dependent are run for each. The rest are just run against Product. This should reduce the runtime to around 5 minutes once BugTaskFlat rules over all.

To post a comment you must log in.
Steve Kowalik (stevenk) wrote :

LAND IT! NOW!

review: Approve (code)

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 2012-04-18 06:07:21 +0000
3+++ lib/lp/bugs/tests/test_bugtask_search.py 2012-04-19 05:53:20 +0000
4@@ -48,7 +48,6 @@
5
6
7 class SearchTestBase:
8- """A mixin class with tests useful for all targets and search variants."""
9
10 layer = LaunchpadFunctionalLayer
11
12@@ -63,27 +62,15 @@
13 expected = self.resultValuesForBugtasks(expected_bugtasks)
14 self.assertEqual(expected, search_result)
15
16- def test_aggregate_by_target(self):
17- # BugTaskSet.search supports returning the counts for each target (as
18- # long as only one type of target was selected).
19- if self.group_on is None:
20- # Not a useful/valid permutation.
21- return
22- self.getBugTaskSearchParams(user=None, multitarget=True)
23- # The test data has 3 bugs for searchtarget and 6 for searchtarget2.
24- user = self.factory.makePerson()
25- expected = {(self.targetToGroup(self.searchtarget),): 3,
26- (self.targetToGroup(self.searchtarget2),): 6}
27- actual = self.bugtask_set.countBugs(
28- user, (self.searchtarget, self.searchtarget2),
29- group_on=self.group_on)
30- self.assertEqual(expected, actual)
31-
32- def test_search_all_bugtasks_for_target(self):
33- # BugTaskSet.search() returns all bug tasks for a given bug
34- # target, if only the bug target is passed as a search parameter.
35- params = self.getBugTaskSearchParams(user=None)
36- self.assertSearchFinds(params, self.bugtasks)
37+ def subscribeToTarget(self, subscriber):
38+ # Subscribe the given person to the search target.
39+ with person_logged_in(subscriber):
40+ self.searchtarget.addSubscription(
41+ subscriber, subscribed_by=subscriber)
42+
43+
44+class OnceTests:
45+ """A mixin class with tests that don't need to be run for all targets."""
46
47 def test_private_bug_in_search_result_anonymous_users(self):
48 # Private bugs are not included in search results for anonymous users.
49@@ -175,68 +162,6 @@
50 params = self.getBugTaskSearchParams(user=None, subscriber=subscriber)
51 self.assertSearchFinds(params, [expected])
52
53- def subscribeToTarget(self, subscriber):
54- # Subscribe the given person to the search target.
55- with person_logged_in(subscriber):
56- self.searchtarget.addSubscription(
57- subscriber, subscribed_by=subscriber)
58-
59- def _findBugtaskForOtherProduct(self, bugtask, main_product):
60- # Return the bugtask for the product that is not related to the
61- # main bug target.
62- #
63- # The default bugtasks of this test suite are created by
64- # ObjectFactory.makeBugTask() as follows:
65- # - a new bug is created having a new product as the target.
66- # - another bugtask is created for self.searchtarget (or,
67- # when self.searchtarget is a milestone, for the product
68- # of the milestone)
69- # This method returns the bug task for the product that is not
70- # related to the main bug target.
71- bug = bugtask.bug
72- for other_task in bug.bugtasks:
73- other_target = other_task.target
74- if (IProduct.providedBy(other_target)
75- and other_target != main_product):
76- return other_task
77- self.fail(
78- 'No bug task found for a product that is not the target of '
79- 'the main test bugtask.')
80-
81- def findBugtaskForOtherProduct(self, bugtask):
82- # Return the bugtask for the product that is not related to the
83- # main bug target.
84- #
85- # This method must ober overridden for product related tests.
86- return self._findBugtaskForOtherProduct(bugtask, None)
87-
88- def test_search_by_structural_subscriber(self):
89- # Search results can be limited to bugs with a bug target to which
90- # a given person has a structural subscription.
91- subscriber = self.factory.makePerson()
92- # If the given person is not subscribed, no bugtasks are returned.
93- params = self.getBugTaskSearchParams(
94- user=None, structural_subscriber=subscriber)
95- self.assertSearchFinds(params, [])
96- # When the person is subscribed, all bugtasks are returned.
97- self.subscribeToTarget(subscriber)
98- params = self.getBugTaskSearchParams(
99- user=None, structural_subscriber=subscriber)
100- self.assertSearchFinds(params, self.bugtasks)
101-
102- # Searching for a structural subscriber does not return a bugtask,
103- # if the person is subscribed to another target than the main
104- # bug target.
105- other_subscriber = self.factory.makePerson()
106- other_bugtask = self.findBugtaskForOtherProduct(self.bugtasks[0])
107- other_target = other_bugtask.target
108- with person_logged_in(other_subscriber):
109- other_target.addSubscription(
110- other_subscriber, subscribed_by=other_subscriber)
111- params = self.getBugTaskSearchParams(
112- user=None, structural_subscriber=other_subscriber)
113- self.assertSearchFinds(params, [])
114-
115 def test_search_by_bug_attachment(self):
116 # Search results can be limited to bugs having attachments of
117 # a given type.
118@@ -286,80 +211,6 @@
119 user=None, fast_searchtext=u'one title')
120 self.assertSearchFinds(params, self.bugtasks[:1])
121
122- def test_has_no_upstream_bugtask(self):
123- # Search results can be limited to bugtasks of bugs that do
124- # not have a related upstream task.
125- #
126- # All bugs created in makeBugTasks() have at least one
127- # bug task for a product: The default bug task created
128- # by lp.testing.factory.Factory.makeBug() if neither a
129- # product nor a distribution is specified. For distribution
130- # related tests we need another bug which does not have
131- # an upstream (aka product) bug task, otherwise the set of
132- # bugtasks returned for a search for has_no_upstream_bugtask
133- # would always be empty.
134- if (IDistribution.providedBy(self.searchtarget) or
135- ISourcePackage.providedBy(self.searchtarget) or
136- IDistributionSourcePackage.providedBy(self.searchtarget)):
137- if IDistribution.providedBy(self.searchtarget):
138- bug = self.factory.makeBug(distribution=self.searchtarget)
139- expected = [bug.default_bugtask]
140- else:
141- bug = self.factory.makeBug(
142- distribution=self.searchtarget.distribution,
143- sourcepackagename=self.factory.makeSourcePackageName())
144- bugtask = self.factory.makeBugTask(
145- bug=bug, target=self.searchtarget)
146- expected = [bugtask]
147- elif IDistroSeries.providedBy(self.searchtarget):
148- bug = self.factory.makeBug(
149- distribution=self.searchtarget.distribution)
150- bugtask = self.factory.makeBugTask(
151- bug=bug, target=self.searchtarget)
152- expected = [bugtask]
153- else:
154- # Bugs without distribution related bugtasks have always at
155- # least one product related bugtask, hence a
156- # has_no_upstream_bugtask search will always return an
157- # empty result set.
158- expected = []
159- params = self.getBugTaskSearchParams(
160- user=None, has_no_upstream_bugtask=True)
161- self.assertSearchFinds(params, expected)
162-
163- def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
164- # Change the status of another bugtask of the same bug to the
165- # given status.
166- other_task = self.findBugtaskForOtherProduct(bugtask)
167- with person_logged_in(other_task.target.owner):
168- other_task.transitionToStatus(new_status, other_task.target.owner)
169-
170- def test_upstream_status(self):
171- # Search results can be filtered by the status of an upstream
172- # bug task.
173- #
174- # The bug task status of the default test data has only bug tasks
175- # with status NEW for the "other" product, hence all bug tasks
176- # will be returned in a search for bugs that are open upstream.
177- params = self.getBugTaskSearchParams(user=None, open_upstream=True)
178- self.assertSearchFinds(params, self.bugtasks)
179- # A search for tasks resolved upstream does not yield any bugtask.
180- params = self.getBugTaskSearchParams(
181- user=None, resolved_upstream=True)
182- self.assertSearchFinds(params, [])
183- # But if we set upstream bug tasks to "fix committed" or "fix
184- # released", the related bug tasks for our test target appear in
185- # the search result.
186- self.changeStatusOfBugTaskForOtherProduct(
187- self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
188- self.changeStatusOfBugTaskForOtherProduct(
189- self.bugtasks[1], BugTaskStatus.FIXRELEASED)
190- self.assertSearchFinds(params, self.bugtasks[:2])
191- # A search for bug tasks open upstream now returns only one
192- # test task.
193- params = self.getBugTaskSearchParams(user=None, open_upstream=True)
194- self.assertSearchFinds(params, self.bugtasks[2:])
195-
196 def test_tags(self):
197 # Search results can be limited to bugs having given tags.
198 with person_logged_in(self.owner):
199@@ -590,6 +441,162 @@
200 self.assertSearchFinds(params, expected)
201
202
203+class TargetTests:
204+ """Tests which are useful for every target."""
205+
206+ def test_aggregate_by_target(self):
207+ # BugTaskSet.search supports returning the counts for each target (as
208+ # long as only one type of target was selected).
209+ if self.group_on is None:
210+ # Not a useful/valid permutation.
211+ return
212+ self.getBugTaskSearchParams(user=None, multitarget=True)
213+ # The test data has 3 bugs for searchtarget and 6 for searchtarget2.
214+ user = self.factory.makePerson()
215+ expected = {(self.targetToGroup(self.searchtarget),): 3,
216+ (self.targetToGroup(self.searchtarget2),): 6}
217+ actual = self.bugtask_set.countBugs(
218+ user, (self.searchtarget, self.searchtarget2),
219+ group_on=self.group_on)
220+ self.assertEqual(expected, actual)
221+
222+ def test_search_all_bugtasks_for_target(self):
223+ # BugTaskSet.search() returns all bug tasks for a given bug
224+ # target, if only the bug target is passed as a search parameter.
225+ params = self.getBugTaskSearchParams(user=None)
226+ self.assertSearchFinds(params, self.bugtasks)
227+
228+ def _findBugtaskForOtherProduct(self, bugtask, main_product):
229+ # Return the bugtask for the product that is not related to the
230+ # main bug target.
231+ #
232+ # The default bugtasks of this test suite are created by
233+ # ObjectFactory.makeBugTask() as follows:
234+ # - a new bug is created having a new product as the target.
235+ # - another bugtask is created for self.searchtarget (or,
236+ # when self.searchtarget is a milestone, for the product
237+ # of the milestone)
238+ # This method returns the bug task for the product that is not
239+ # related to the main bug target.
240+ bug = bugtask.bug
241+ for other_task in bug.bugtasks:
242+ other_target = other_task.target
243+ if (IProduct.providedBy(other_target)
244+ and other_target != main_product):
245+ return other_task
246+ self.fail(
247+ 'No bug task found for a product that is not the target of '
248+ 'the main test bugtask.')
249+
250+ def findBugtaskForOtherProduct(self, bugtask):
251+ # Return the bugtask for the product that is not related to the
252+ # main bug target.
253+ #
254+ # This method must ober overridden for product related tests.
255+ return self._findBugtaskForOtherProduct(bugtask, None)
256+
257+ def test_search_by_structural_subscriber(self):
258+ # Search results can be limited to bugs with a bug target to which
259+ # a given person has a structural subscription.
260+ subscriber = self.factory.makePerson()
261+ # If the given person is not subscribed, no bugtasks are returned.
262+ params = self.getBugTaskSearchParams(
263+ user=None, structural_subscriber=subscriber)
264+ self.assertSearchFinds(params, [])
265+ # When the person is subscribed, all bugtasks are returned.
266+ self.subscribeToTarget(subscriber)
267+ params = self.getBugTaskSearchParams(
268+ user=None, structural_subscriber=subscriber)
269+ self.assertSearchFinds(params, self.bugtasks)
270+
271+ # Searching for a structural subscriber does not return a bugtask,
272+ # if the person is subscribed to another target than the main
273+ # bug target.
274+ other_subscriber = self.factory.makePerson()
275+ other_bugtask = self.findBugtaskForOtherProduct(self.bugtasks[0])
276+ other_target = other_bugtask.target
277+ with person_logged_in(other_subscriber):
278+ other_target.addSubscription(
279+ other_subscriber, subscribed_by=other_subscriber)
280+ params = self.getBugTaskSearchParams(
281+ user=None, structural_subscriber=other_subscriber)
282+ self.assertSearchFinds(params, [])
283+
284+ def test_has_no_upstream_bugtask(self):
285+ # Search results can be limited to bugtasks of bugs that do
286+ # not have a related upstream task.
287+ #
288+ # All bugs created in makeBugTasks() have at least one
289+ # bug task for a product: The default bug task created
290+ # by lp.testing.factory.Factory.makeBug() if neither a
291+ # product nor a distribution is specified. For distribution
292+ # related tests we need another bug which does not have
293+ # an upstream (aka product) bug task, otherwise the set of
294+ # bugtasks returned for a search for has_no_upstream_bugtask
295+ # would always be empty.
296+ if (IDistribution.providedBy(self.searchtarget) or
297+ ISourcePackage.providedBy(self.searchtarget) or
298+ IDistributionSourcePackage.providedBy(self.searchtarget)):
299+ if IDistribution.providedBy(self.searchtarget):
300+ bug = self.factory.makeBug(distribution=self.searchtarget)
301+ expected = [bug.default_bugtask]
302+ else:
303+ bug = self.factory.makeBug(
304+ distribution=self.searchtarget.distribution,
305+ sourcepackagename=self.factory.makeSourcePackageName())
306+ bugtask = self.factory.makeBugTask(
307+ bug=bug, target=self.searchtarget)
308+ expected = [bugtask]
309+ elif IDistroSeries.providedBy(self.searchtarget):
310+ bug = self.factory.makeBug(
311+ distribution=self.searchtarget.distribution)
312+ bugtask = self.factory.makeBugTask(
313+ bug=bug, target=self.searchtarget)
314+ expected = [bugtask]
315+ else:
316+ # Bugs without distribution related bugtasks have always at
317+ # least one product related bugtask, hence a
318+ # has_no_upstream_bugtask search will always return an
319+ # empty result set.
320+ expected = []
321+ params = self.getBugTaskSearchParams(
322+ user=None, has_no_upstream_bugtask=True)
323+ self.assertSearchFinds(params, expected)
324+
325+ def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
326+ # Change the status of another bugtask of the same bug to the
327+ # given status.
328+ other_task = self.findBugtaskForOtherProduct(bugtask)
329+ with person_logged_in(other_task.target.owner):
330+ other_task.transitionToStatus(new_status, other_task.target.owner)
331+
332+ def test_upstream_status(self):
333+ # Search results can be filtered by the status of an upstream
334+ # bug task.
335+ #
336+ # The bug task status of the default test data has only bug tasks
337+ # with status NEW for the "other" product, hence all bug tasks
338+ # will be returned in a search for bugs that are open upstream.
339+ params = self.getBugTaskSearchParams(user=None, open_upstream=True)
340+ self.assertSearchFinds(params, self.bugtasks)
341+ # A search for tasks resolved upstream does not yield any bugtask.
342+ params = self.getBugTaskSearchParams(
343+ user=None, resolved_upstream=True)
344+ self.assertSearchFinds(params, [])
345+ # But if we set upstream bug tasks to "fix committed" or "fix
346+ # released", the related bug tasks for our test target appear in
347+ # the search result.
348+ self.changeStatusOfBugTaskForOtherProduct(
349+ self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
350+ self.changeStatusOfBugTaskForOtherProduct(
351+ self.bugtasks[1], BugTaskStatus.FIXRELEASED)
352+ self.assertSearchFinds(params, self.bugtasks[:2])
353+ # A search for bug tasks open upstream now returns only one
354+ # test task.
355+ params = self.getBugTaskSearchParams(user=None, open_upstream=True)
356+ self.assertSearchFinds(params, self.bugtasks[2:])
357+
358+
359 class DeactivatedProductBugTaskTestCase(TestCaseWithFactory):
360
361 layer = DatabaseFunctionalLayer
362@@ -1576,8 +1583,19 @@
363 loader = unittest.TestLoader()
364 for bug_target_search_type_class in (
365 PreloadBugtaskTargets, NoPreloadBugtaskTargets, QueryBugIDs):
366- for target_mixin in bug_targets_mixins:
367- for feature_mixin in (UsingLegacy, UsingFlat):
368+ for feature_mixin in (UsingLegacy, UsingFlat):
369+ class_name = 'Test%s%s' % (
370+ bug_target_search_type_class.__name__,
371+ feature_mixin.__name__)
372+ mixins = [bug_target_search_type_class, feature_mixin]
373+ class_bases = (
374+ tuple(mixins)
375+ + (ProductTarget, OnceTests, SearchTestBase,
376+ TestCaseWithFactory))
377+ test_class = type(class_name, class_bases, {})
378+ suite.addTest(loader.loadTestsFromTestCase(test_class))
379+
380+ for target_mixin in bug_targets_mixins:
381 class_name = 'Test%s%s%s' % (
382 bug_target_search_type_class.__name__,
383 target_mixin.__name__,
384@@ -1585,7 +1603,8 @@
385 mixins = [
386 target_mixin, bug_target_search_type_class, feature_mixin]
387 class_bases = (
388- tuple(mixins) + (SearchTestBase, TestCaseWithFactory))
389+ tuple(mixins)
390+ + (TargetTests, SearchTestBase, TestCaseWithFactory))
391 # Dynamically build a test class from the target mixin class,
392 # from the search type mixin class, from the mixin class
393 # having all tests and from a unit test base class.