Merge lp:~cjwatson/launchpad/preload-git-landing-targets-candidates into lp:launchpad
- preload-git-landing-targets-candidates
- Merge into devel
Proposed by
Colin Watson
Status: | Merged |
---|---|
Merged at revision: | 18285 |
Proposed branch: | lp:~cjwatson/launchpad/preload-git-landing-targets-candidates |
Merge into: | lp:launchpad |
Prerequisite: | lp:~cjwatson/launchpad/preload-branch-landing-targets |
Diff against target: |
541 lines (+268/-48) 9 files modified
lib/lp/_schema_circular_imports.py (+6/-4) lib/lp/code/browser/gitref.py (+4/-2) lib/lp/code/browser/tests/test_gitref.py (+36/-0) lib/lp/code/interfaces/gitref.py (+40/-16) lib/lp/code/interfaces/gitrepository.py (+40/-16) lib/lp/code/model/gitref.py (+20/-10) lib/lp/code/model/gitrepository.py (+20/-0) lib/lp/code/model/tests/test_gitref.py (+51/-0) lib/lp/code/model/tests/test_gitrepository.py (+51/-0) |
To merge this branch: | bzr merge lp:~cjwatson/launchpad/preload-git-landing-targets-candidates |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+310655@code.launchpad.net |
Commit message
Preload {GitRepository,
Description of the change
This is in line with similar handling in Branch.
To post a comment you must log in.
Revision history for this message
William Grant (wgrant) : | # |
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/_schema_circular_imports.py' |
2 | --- lib/lp/_schema_circular_imports.py 2016-11-11 15:10:55 +0000 |
3 | +++ lib/lp/_schema_circular_imports.py 2016-11-11 15:10:56 +0000 |
4 | @@ -566,8 +566,10 @@ |
5 | IGitRef, 'createMergeProposal', 'merge_target', IGitRef) |
6 | patch_plain_parameter_type( |
7 | IGitRef, 'createMergeProposal', 'merge_prerequisite', IGitRef) |
8 | -patch_collection_property(IGitRef, 'landing_targets', IBranchMergeProposal) |
9 | -patch_collection_property(IGitRef, 'landing_candidates', IBranchMergeProposal) |
10 | +patch_collection_property( |
11 | + IGitRef, '_api_landing_targets', IBranchMergeProposal) |
12 | +patch_collection_property( |
13 | + IGitRef, '_api_landing_candidates', IBranchMergeProposal) |
14 | patch_collection_property(IGitRef, 'dependent_landings', IBranchMergeProposal) |
15 | patch_entry_return_type(IGitRef, 'createMergeProposal', IBranchMergeProposal) |
16 | patch_collection_return_type( |
17 | @@ -581,9 +583,9 @@ |
18 | patch_entry_return_type(IGitRepository, 'getSubscription', IGitSubscription) |
19 | patch_reference_property(IGitRepository, 'code_import', ICodeImport) |
20 | patch_collection_property( |
21 | - IGitRepository, 'landing_targets', IBranchMergeProposal) |
22 | + IGitRepository, '_api_landing_targets', IBranchMergeProposal) |
23 | patch_collection_property( |
24 | - IGitRepository, 'landing_candidates', IBranchMergeProposal) |
25 | + IGitRepository, '_api_landing_candidates', IBranchMergeProposal) |
26 | patch_collection_property( |
27 | IGitRepository, 'dependent_landings', IBranchMergeProposal) |
28 | |
29 | |
30 | === modified file 'lib/lp/code/browser/gitref.py' |
31 | --- lib/lp/code/browser/gitref.py 2016-10-14 16:16:18 +0000 |
32 | +++ lib/lp/code/browser/gitref.py 2016-11-11 15:10:56 +0000 |
33 | @@ -133,12 +133,14 @@ |
34 | @cachedproperty |
35 | def landing_targets(self): |
36 | """Return a filtered list of landing targets.""" |
37 | - return latest_proposals_for_each_branch(self.context.landing_targets) |
38 | + targets = self.context.getPrecachedLandingTargets(self.user) |
39 | + return latest_proposals_for_each_branch(targets) |
40 | |
41 | @cachedproperty |
42 | def landing_candidates(self): |
43 | """Return a decorated list of landing candidates.""" |
44 | - return [proposal for proposal in self.context.landing_candidates |
45 | + candidates = self.context.getPrecachedLandingCandidates(self.user) |
46 | + return [proposal for proposal in candidates |
47 | if check_permission("launchpad.View", proposal)] |
48 | |
49 | def _getBranchCountText(self, count): |
50 | |
51 | === modified file 'lib/lp/code/browser/tests/test_gitref.py' |
52 | --- lib/lp/code/browser/tests/test_gitref.py 2016-09-07 11:12:58 +0000 |
53 | +++ lib/lp/code/browser/tests/test_gitref.py 2016-11-11 15:10:56 +0000 |
54 | @@ -12,6 +12,8 @@ |
55 | from BeautifulSoup import BeautifulSoup |
56 | import pytz |
57 | import soupmatchers |
58 | +from storm.store import Store |
59 | +from testtools.matchers import Equals |
60 | from zope.component import getUtility |
61 | from zope.security.proxy import removeSecurityProxy |
62 | |
63 | @@ -23,9 +25,11 @@ |
64 | from lp.testing import ( |
65 | admin_logged_in, |
66 | BrowserTestCase, |
67 | + StormStatementRecorder, |
68 | ) |
69 | from lp.testing.dbuser import dbuser |
70 | from lp.testing.layers import LaunchpadFunctionalLayer |
71 | +from lp.testing.matchers import HasQueryCount |
72 | from lp.testing.pages import ( |
73 | extract_text, |
74 | find_tags_by_class, |
75 | @@ -220,3 +224,35 @@ |
76 | soupmatchers.Tag( |
77 | 'all commits link', 'a', text='All commits', |
78 | attrs={'href': expected_url})))) |
79 | + |
80 | + def test_query_count_landing_candidates(self): |
81 | + project = self.factory.makeProduct() |
82 | + [ref] = self.factory.makeGitRefs(target=project) |
83 | + for i in range(10): |
84 | + self.factory.makeBranchMergeProposalForGit(target_ref=ref) |
85 | + [source] = self.factory.makeGitRefs(target=project) |
86 | + [prereq] = self.factory.makeGitRefs(target=project) |
87 | + self.factory.makeBranchMergeProposalForGit( |
88 | + source_ref=source, target_ref=ref, prerequisite_ref=prereq) |
89 | + Store.of(ref).flush() |
90 | + Store.of(ref).invalidate() |
91 | + view = create_view(ref, '+index') |
92 | + with StormStatementRecorder() as recorder: |
93 | + view.landing_candidates |
94 | + self.assertThat(recorder, HasQueryCount(Equals(11))) |
95 | + |
96 | + def test_query_count_landing_targets(self): |
97 | + project = self.factory.makeProduct() |
98 | + [ref] = self.factory.makeGitRefs(target=project) |
99 | + for i in range(10): |
100 | + self.factory.makeBranchMergeProposalForGit(source_ref=ref) |
101 | + [target] = self.factory.makeGitRefs(target=project) |
102 | + [prereq] = self.factory.makeGitRefs(target=project) |
103 | + self.factory.makeBranchMergeProposalForGit( |
104 | + source_ref=ref, target_ref=target, prerequisite_ref=prereq) |
105 | + Store.of(ref).flush() |
106 | + Store.of(ref).invalidate() |
107 | + view = create_view(ref, '+index') |
108 | + with StormStatementRecorder() as recorder: |
109 | + view.landing_targets |
110 | + self.assertThat(recorder, HasQueryCount(Equals(11))) |
111 | |
112 | === modified file 'lib/lp/code/interfaces/gitref.py' |
113 | --- lib/lp/code/interfaces/gitref.py 2016-11-09 17:18:21 +0000 |
114 | +++ lib/lp/code/interfaces/gitref.py 2016-11-11 15:10:56 +0000 |
115 | @@ -221,22 +221,34 @@ |
116 | and their subscriptions. |
117 | """ |
118 | |
119 | - landing_targets = exported(CollectionField( |
120 | - title=_("Landing targets"), |
121 | - description=_( |
122 | - "A collection of the merge proposals where this reference is the " |
123 | - "source."), |
124 | - readonly=True, |
125 | - # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
126 | - value_type=Reference(Interface))) |
127 | - landing_candidates = exported(CollectionField( |
128 | - title=_("Landing candidates"), |
129 | - description=_( |
130 | - "A collection of the merge proposals where this reference is the " |
131 | - "target."), |
132 | - readonly=True, |
133 | - # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
134 | - value_type=Reference(Interface))) |
135 | + landing_targets = Attribute( |
136 | + "A collection of the merge proposals where this reference is " |
137 | + "the source.") |
138 | + _api_landing_targets = exported( |
139 | + CollectionField( |
140 | + title=_("Landing targets"), |
141 | + description=_( |
142 | + "A collection of the merge proposals where this reference is " |
143 | + "the source."), |
144 | + readonly=True, |
145 | + # Really IBranchMergeProposal, patched in |
146 | + # _schema_circular_imports.py. |
147 | + value_type=Reference(Interface)), |
148 | + exported_as="landing_targets") |
149 | + landing_candidates = Attribute( |
150 | + "A collection of the merge proposals where this reference is " |
151 | + "the target.") |
152 | + _api_landing_candidates = exported( |
153 | + CollectionField( |
154 | + title=_("Landing candidates"), |
155 | + description=_( |
156 | + "A collection of the merge proposals where this reference is " |
157 | + "the target."), |
158 | + readonly=True, |
159 | + # Really IBranchMergeProposal, patched in |
160 | + # _schema_circular_imports.py. |
161 | + value_type=Reference(Interface)), |
162 | + exported_as="landing_candidates") |
163 | dependent_landings = exported(CollectionField( |
164 | title=_("Dependent landings"), |
165 | description=_( |
166 | @@ -246,6 +258,18 @@ |
167 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
168 | value_type=Reference(Interface))) |
169 | |
170 | + def getPrecachedLandingTargets(user): |
171 | + """Return precached landing targets. |
172 | + |
173 | + Target and prerequisite repositories are preloaded. |
174 | + """ |
175 | + |
176 | + def getPrecachedLandingCandidates(user): |
177 | + """Return precached landing candidates. |
178 | + |
179 | + Source and prerequisite repositories are preloaded. |
180 | + """ |
181 | + |
182 | # XXX cjwatson 2015-04-16: Rename in line with landing_targets above |
183 | # once we have a better name. |
184 | def addLandingTarget(registrant, merge_target, merge_prerequisite=None, |
185 | |
186 | === modified file 'lib/lp/code/interfaces/gitrepository.py' |
187 | --- lib/lp/code/interfaces/gitrepository.py 2016-10-14 15:08:36 +0000 |
188 | +++ lib/lp/code/interfaces/gitrepository.py 2016-11-11 15:10:56 +0000 |
189 | @@ -476,22 +476,34 @@ |
190 | and their subscriptions. |
191 | """ |
192 | |
193 | - landing_targets = exported(CollectionField( |
194 | - title=_("Landing targets"), |
195 | - description=_( |
196 | - "A collection of the merge proposals where this repository is the " |
197 | - "source."), |
198 | - readonly=True, |
199 | - # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
200 | - value_type=Reference(Interface))) |
201 | - landing_candidates = exported(CollectionField( |
202 | - title=_("Landing candidates"), |
203 | - description=_( |
204 | - "A collection of the merge proposals where this repository is the " |
205 | - "target."), |
206 | - readonly=True, |
207 | - # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
208 | - value_type=Reference(Interface))) |
209 | + landing_targets = Attribute( |
210 | + "A collection of the merge proposals where this repository is " |
211 | + "the source.") |
212 | + _api_landing_targets = exported( |
213 | + CollectionField( |
214 | + title=_("Landing targets"), |
215 | + description=_( |
216 | + "A collection of the merge proposals where this repository is " |
217 | + "the source."), |
218 | + readonly=True, |
219 | + # Really IBranchMergeProposal, patched in |
220 | + # _schema_circular_imports.py. |
221 | + value_type=Reference(Interface)), |
222 | + exported_as="landing_targets") |
223 | + landing_candidates = Attribute( |
224 | + "A collection of the merge proposals where this repository is " |
225 | + "the target.") |
226 | + _api_landing_candidates = exported( |
227 | + CollectionField( |
228 | + title=_("Landing candidates"), |
229 | + description=_( |
230 | + "A collection of the merge proposals where this repository is " |
231 | + "the target."), |
232 | + readonly=True, |
233 | + # Really IBranchMergeProposal, patched in |
234 | + # _schema_circular_imports.py. |
235 | + value_type=Reference(Interface)), |
236 | + exported_as="landing_candidates") |
237 | dependent_landings = exported(CollectionField( |
238 | title=_("Dependent landings"), |
239 | description=_( |
240 | @@ -501,6 +513,18 @@ |
241 | # Really IBranchMergeProposal, patched in _schema_circular_imports.py. |
242 | value_type=Reference(Interface))) |
243 | |
244 | + def getPrecachedLandingTargets(user): |
245 | + """Return precached landing targets. |
246 | + |
247 | + Target and prerequisite repositories are preloaded. |
248 | + """ |
249 | + |
250 | + def getPrecachedLandingCandidates(user): |
251 | + """Return precached landing candidates. |
252 | + |
253 | + Source and prerequisite repositories are preloaded. |
254 | + """ |
255 | + |
256 | def getMergeProposalByID(id): |
257 | """Return this repository's merge proposal with this id, or None.""" |
258 | |
259 | |
260 | === modified file 'lib/lp/code/model/gitref.py' |
261 | --- lib/lp/code/model/gitref.py 2016-11-09 17:18:21 +0000 |
262 | +++ lib/lp/code/model/gitref.py 2016-11-11 15:10:56 +0000 |
263 | @@ -8,6 +8,7 @@ |
264 | ] |
265 | |
266 | from datetime import datetime |
267 | +from functools import partial |
268 | import json |
269 | from urllib import quote_plus |
270 | from urlparse import urlsplit |
271 | @@ -51,7 +52,6 @@ |
272 | BranchMergeProposalGetter, |
273 | ) |
274 | from lp.services.config import config |
275 | -from lp.services.database.bulk import load_related |
276 | from lp.services.database.constants import UTC_NOW |
277 | from lp.services.database.decoratedresultset import DecoratedResultSet |
278 | from lp.services.database.enumcol import EnumCol |
279 | @@ -59,6 +59,7 @@ |
280 | from lp.services.database.stormbase import StormBase |
281 | from lp.services.features import getFeatureFlag |
282 | from lp.services.memcache.interfaces import IMemcacheClient |
283 | +from lp.services.webapp.interfaces import ILaunchBag |
284 | |
285 | |
286 | class GitRefMixin: |
287 | @@ -194,25 +195,34 @@ |
288 | BranchMergeProposal.source_git_repository == self.repository, |
289 | BranchMergeProposal.source_git_path == self.path) |
290 | |
291 | + def getPrecachedLandingTargets(self, user): |
292 | + """See `IGitRef`.""" |
293 | + loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
294 | + return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader) |
295 | + |
296 | + @property |
297 | + def _api_landing_targets(self): |
298 | + return self.getPrecachedLandingTargets(getUtility(ILaunchBag).user) |
299 | + |
300 | @property |
301 | def landing_candidates(self): |
302 | """See `IGitRef`.""" |
303 | - # Circular import. |
304 | - from lp.code.model.gitrepository import GitRepository |
305 | - |
306 | - result = Store.of(self).find( |
307 | + return Store.of(self).find( |
308 | BranchMergeProposal, |
309 | BranchMergeProposal.target_git_repository == self.repository, |
310 | BranchMergeProposal.target_git_path == self.path, |
311 | Not(BranchMergeProposal.queue_status.is_in( |
312 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) |
313 | |
314 | - def eager_load(rows): |
315 | - load_related( |
316 | - GitRepository, rows, |
317 | - ["source_git_repositoryID", "prerequisite_git_repositoryID"]) |
318 | + def getPrecachedLandingCandidates(self, user): |
319 | + """See `IGitRef`.""" |
320 | + loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
321 | + return DecoratedResultSet( |
322 | + self.landing_candidates, pre_iter_hook=loader) |
323 | |
324 | - return DecoratedResultSet(result, pre_iter_hook=eager_load) |
325 | + @property |
326 | + def _api_landing_candidates(self): |
327 | + return self.getPrecachedLandingCandidates(getUtility(ILaunchBag).user) |
328 | |
329 | @property |
330 | def dependent_landings(self): |
331 | |
332 | === modified file 'lib/lp/code/model/gitrepository.py' |
333 | --- lib/lp/code/model/gitrepository.py 2016-10-14 15:08:58 +0000 |
334 | +++ lib/lp/code/model/gitrepository.py 2016-11-11 15:10:56 +0000 |
335 | @@ -155,6 +155,7 @@ |
336 | get_property_cache, |
337 | ) |
338 | from lp.services.webapp.authorization import available_with_permission |
339 | +from lp.services.webapp.interfaces import ILaunchBag |
340 | from lp.services.webhooks.interfaces import IWebhookSet |
341 | from lp.services.webhooks.model import WebhookTargetMixin |
342 | from lp.snappy.interfaces.snap import ISnapSet |
343 | @@ -887,6 +888,15 @@ |
344 | BranchMergeProposal, |
345 | BranchMergeProposal.source_git_repository == self) |
346 | |
347 | + def getPrecachedLandingTargets(self, user): |
348 | + """See `IGitRef`.""" |
349 | + loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
350 | + return DecoratedResultSet(self.landing_targets, pre_iter_hook=loader) |
351 | + |
352 | + @property |
353 | + def _api_landing_targets(self): |
354 | + return self.getPrecachedLandingTargets(getUtility(ILaunchBag).user) |
355 | + |
356 | def getActiveLandingTargets(self, paths): |
357 | """Merge proposals not in final states where these refs are source.""" |
358 | return Store.of(self).find( |
359 | @@ -905,6 +915,16 @@ |
360 | Not(BranchMergeProposal.queue_status.is_in( |
361 | BRANCH_MERGE_PROPOSAL_FINAL_STATES))) |
362 | |
363 | + def getPrecachedLandingCandidates(self, user): |
364 | + """See `IGitRef`.""" |
365 | + loader = partial(BranchMergeProposal.preloadDataForBMPs, user=user) |
366 | + return DecoratedResultSet( |
367 | + self.landing_candidates, pre_iter_hook=loader) |
368 | + |
369 | + @property |
370 | + def _api_landing_candidates(self): |
371 | + return self.getPrecachedLandingCandidates(getUtility(ILaunchBag).user) |
372 | + |
373 | def getActiveLandingCandidates(self, paths): |
374 | """Merge proposals not in final states where these refs are target.""" |
375 | return Store.of(self).find( |
376 | |
377 | === modified file 'lib/lp/code/model/tests/test_gitref.py' |
378 | --- lib/lp/code/model/tests/test_gitref.py 2016-09-07 11:12:58 +0000 |
379 | +++ lib/lp/code/model/tests/test_gitref.py 2016-11-11 15:10:56 +0000 |
380 | @@ -18,6 +18,7 @@ |
381 | EndsWith, |
382 | Equals, |
383 | Is, |
384 | + LessThan, |
385 | MatchesListwise, |
386 | MatchesStructure, |
387 | ) |
388 | @@ -36,6 +37,7 @@ |
389 | ANONYMOUS, |
390 | api_url, |
391 | person_logged_in, |
392 | + record_two_runs, |
393 | TestCaseWithFactory, |
394 | verifyObject, |
395 | ) |
396 | @@ -43,6 +45,7 @@ |
397 | DatabaseFunctionalLayer, |
398 | LaunchpadFunctionalLayer, |
399 | ) |
400 | +from lp.testing.matchers import HasQueryCount |
401 | from lp.testing.pages import webservice_for_person |
402 | |
403 | |
404 | @@ -461,6 +464,30 @@ |
405 | self.assertThat( |
406 | landing_candidates["entries"][0]["self_link"], EndsWith(bmp_url)) |
407 | |
408 | + def test_landing_candidates_constant_queries(self): |
409 | + project = self.factory.makeProduct() |
410 | + with person_logged_in(project.owner): |
411 | + [trunk] = self.factory.makeGitRefs(target=project) |
412 | + trunk_url = api_url(trunk) |
413 | + webservice = webservice_for_person( |
414 | + project.owner, permission=OAuthPermission.WRITE_PRIVATE) |
415 | + |
416 | + def create_mp(): |
417 | + with admin_logged_in(): |
418 | + [ref] = self.factory.makeGitRefs( |
419 | + target=project, |
420 | + information_type=InformationType.PRIVATESECURITY) |
421 | + self.factory.makeBranchMergeProposalForGit( |
422 | + source_ref=ref, target_ref=trunk) |
423 | + |
424 | + def list_mps(): |
425 | + webservice.get(trunk_url + "/landing_candidates") |
426 | + |
427 | + list_mps() |
428 | + recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) |
429 | + self.assertThat(recorder1, HasQueryCount(LessThan(30))) |
430 | + self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) |
431 | + |
432 | def test_landing_targets(self): |
433 | bmp_db = self.factory.makeBranchMergeProposalForGit() |
434 | with person_logged_in(bmp_db.registrant): |
435 | @@ -476,6 +503,30 @@ |
436 | self.assertThat( |
437 | landing_targets["entries"][0]["self_link"], EndsWith(bmp_url)) |
438 | |
439 | + def test_landing_targets_constant_queries(self): |
440 | + project = self.factory.makeProduct() |
441 | + with person_logged_in(project.owner): |
442 | + [source] = self.factory.makeGitRefs(target=project) |
443 | + source_url = api_url(source) |
444 | + webservice = webservice_for_person( |
445 | + project.owner, permission=OAuthPermission.WRITE_PRIVATE) |
446 | + |
447 | + def create_mp(): |
448 | + with admin_logged_in(): |
449 | + [ref] = self.factory.makeGitRefs( |
450 | + target=project, |
451 | + information_type=InformationType.PRIVATESECURITY) |
452 | + self.factory.makeBranchMergeProposalForGit( |
453 | + source_ref=source, target_ref=ref) |
454 | + |
455 | + def list_mps(): |
456 | + webservice.get(source_url + "/landing_targets") |
457 | + |
458 | + list_mps() |
459 | + recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) |
460 | + self.assertThat(recorder1, HasQueryCount(LessThan(30))) |
461 | + self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) |
462 | + |
463 | def test_dependent_landings(self): |
464 | [ref] = self.factory.makeGitRefs() |
465 | bmp_db = self.factory.makeBranchMergeProposalForGit( |
466 | |
467 | === modified file 'lib/lp/code/model/tests/test_gitrepository.py' |
468 | --- lib/lp/code/model/tests/test_gitrepository.py 2016-10-14 16:16:18 +0000 |
469 | +++ lib/lp/code/model/tests/test_gitrepository.py 2016-11-11 15:10:56 +0000 |
470 | @@ -20,6 +20,7 @@ |
471 | from storm.store import Store |
472 | from testtools.matchers import ( |
473 | EndsWith, |
474 | + LessThan, |
475 | MatchesSetwise, |
476 | MatchesStructure, |
477 | ) |
478 | @@ -2800,6 +2801,31 @@ |
479 | self.assertThat( |
480 | landing_candidates["entries"][0]["self_link"], EndsWith(bmp_url)) |
481 | |
482 | + def test_landing_candidates_constant_queries(self): |
483 | + project = self.factory.makeProduct() |
484 | + with person_logged_in(project.owner): |
485 | + repository = self.factory.makeGitRepository(target=project) |
486 | + repository_url = api_url(repository) |
487 | + webservice = webservice_for_person( |
488 | + project.owner, permission=OAuthPermission.WRITE_PRIVATE) |
489 | + |
490 | + def create_mp(): |
491 | + with admin_logged_in(): |
492 | + [target] = self.factory.makeGitRefs(repository=repository) |
493 | + [source] = self.factory.makeGitRefs( |
494 | + target=project, |
495 | + information_type=InformationType.PRIVATESECURITY) |
496 | + self.factory.makeBranchMergeProposalForGit( |
497 | + source_ref=source, target_ref=target) |
498 | + |
499 | + def list_mps(): |
500 | + webservice.get(repository_url + "/landing_candidates") |
501 | + |
502 | + list_mps() |
503 | + recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) |
504 | + self.assertThat(recorder1, HasQueryCount(LessThan(40))) |
505 | + self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) |
506 | + |
507 | def test_landing_targets(self): |
508 | bmp_db = self.factory.makeBranchMergeProposalForGit() |
509 | with person_logged_in(bmp_db.registrant): |
510 | @@ -2815,6 +2841,31 @@ |
511 | self.assertThat( |
512 | landing_targets["entries"][0]["self_link"], EndsWith(bmp_url)) |
513 | |
514 | + def test_landing_targets_constant_queries(self): |
515 | + project = self.factory.makeProduct() |
516 | + with person_logged_in(project.owner): |
517 | + repository = self.factory.makeGitRepository(target=project) |
518 | + repository_url = api_url(repository) |
519 | + webservice = webservice_for_person( |
520 | + project.owner, permission=OAuthPermission.WRITE_PRIVATE) |
521 | + |
522 | + def create_mp(): |
523 | + with admin_logged_in(): |
524 | + [source] = self.factory.makeGitRefs(repository=repository) |
525 | + [target] = self.factory.makeGitRefs( |
526 | + target=project, |
527 | + information_type=InformationType.PRIVATESECURITY) |
528 | + self.factory.makeBranchMergeProposalForGit( |
529 | + source_ref=source, target_ref=target) |
530 | + |
531 | + def list_mps(): |
532 | + webservice.get(repository_url + "/landing_targets") |
533 | + |
534 | + list_mps() |
535 | + recorder1, recorder2 = record_two_runs(list_mps, create_mp, 2) |
536 | + self.assertThat(recorder1, HasQueryCount(LessThan(30))) |
537 | + self.assertThat(recorder2, HasQueryCount.byEquality(recorder1)) |
538 | + |
539 | def test_dependent_landings(self): |
540 | [ref] = self.factory.makeGitRefs() |
541 | bmp_db = self.factory.makeBranchMergeProposalForGit( |