Merge lp:~mwhudson/launchpad/in-memory-launchpad-server into lp:launchpad
- in-memory-launchpad-server
- Merge into devel
Proposed by
Michael Hudson-Doyle
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~mwhudson/launchpad/in-memory-launchpad-server |
Merge into: | lp:launchpad |
Diff against target: |
667 lines 14 files modified
lib/lp/code/configure.zcml (+3/-0) lib/lp/code/interfaces/branchlookup.py (+11/-0) lib/lp/code/model/branchlookup.py (+22/-8) lib/lp/code/model/tests/test_branchlookup.py (+43/-0) lib/lp/code/xmlrpc/codehosting.py (+1/-19) lib/lp/code/xmlrpc/tests/test_codehosting.py (+3/-22) lib/lp/codehosting/rewrite.py (+14/-19) lib/lp/codehosting/tests/test_branchdistro.py (+3/-3) lib/lp/codehosting/vfs/branchfs.py (+60/-9) lib/lp/codehosting/vfs/tests/test_branchfs.py (+36/-18) lib/lp/services/tests/test_utils.py (+32/-0) lib/lp/services/utils.py (+29/-0) lib/lp/testing/__init__.py (+4/-2) scripts/branch-distro.py (+2/-1) |
To merge this branch: | bzr merge lp:~mwhudson/launchpad/in-memory-launchpad-server |
Related bugs: | |
Related blueprints: |
Branching Ubuntu on release
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey (community) | Approve | ||
Review via email: mp+13613@code.launchpad.net |
Commit message
Add a way to access branches by querying the database directly rather than via an XML-RPC call, and use it in the branch-distro.py script.
Description of the change
To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote : | # |
Revision history for this message
Tim Penhey (thumper) wrote : | # |
I think this is actually pretty good.
Some of the tests are missing comments/
class name and method name makes it pretty clear what is being tested.
merge approved
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/code/configure.zcml' |
2 | --- lib/lp/code/configure.zcml 2009-10-15 05:45:57 +0000 |
3 | +++ lib/lp/code/configure.zcml 2009-10-20 21:56:15 +0000 |
4 | @@ -355,6 +355,9 @@ |
5 | provides="lp.code.interfaces.branchlookup.ILinkedBranchTraverser"> |
6 | <allow interface="lp.code.interfaces.branchlookup.ILinkedBranchTraverser"/> |
7 | </securedutility> |
8 | + <adapter factory="lp.code.model.branchlookup.ProductTraversable" /> |
9 | + <adapter factory="lp.code.model.branchlookup.DistributionTraversable" /> |
10 | + <adapter factory="lp.code.model.branchlookup.DistroSeriesTraversable" /> |
11 | <securedutility |
12 | class="lp.code.model.branchscanner.BranchScanner" |
13 | provides="lp.code.interfaces.branchscanner.IBranchScanner"> |
14 | |
15 | === modified file 'lib/lp/code/interfaces/branchlookup.py' |
16 | --- lib/lp/code/interfaces/branchlookup.py 2009-07-19 23:17:34 +0000 |
17 | +++ lib/lp/code/interfaces/branchlookup.py 2009-10-20 21:56:15 +0000 |
18 | @@ -68,6 +68,17 @@ |
19 | Return None if no match was found. |
20 | """ |
21 | |
22 | + def getIdAndTrailingPath(self, path, from_slave=False): |
23 | + """Return id of and path within the branch identified by the `path`. |
24 | + |
25 | + To explain by example, if the branch with id 5 has unique name |
26 | + '~user/project/name', getIdAndTrailingPath('~user/project/name/foo') |
27 | + will return (5, '/foo'). |
28 | + |
29 | + :return: ``(branch_id, trailing_path)``, both will be ``None`` if no |
30 | + branch is identified. |
31 | + """ |
32 | + |
33 | def uriToUniqueName(uri): |
34 | """Return the unique name for the URI, if the URI is on codehosting. |
35 | |
36 | |
37 | === modified file 'lib/lp/code/model/branchlookup.py' |
38 | --- lib/lp/code/model/branchlookup.py 2009-07-19 23:17:34 +0000 |
39 | +++ lib/lp/code/model/branchlookup.py 2009-10-20 21:56:15 +0000 |
40 | @@ -8,8 +8,7 @@ |
41 | # then get the IBranchLookup utility. |
42 | __all__ = [] |
43 | |
44 | -from zope.component import ( |
45 | - adapts, getSiteManager, getUtility, queryMultiAdapter) |
46 | +from zope.component import adapts, getUtility, queryMultiAdapter |
47 | from zope.interface import implements |
48 | |
49 | from storm.expr import Join |
50 | @@ -37,7 +36,9 @@ |
51 | from lp.registry.interfaces.productseries import NoSuchProductSeries |
52 | from lp.registry.interfaces.sourcepackagename import ( |
53 | NoSuchSourcePackageName) |
54 | +from lp.services.utils import iter_split |
55 | from canonical.launchpad.validators.name import valid_name |
56 | +from canonical.launchpad.interfaces.lpstorm import IMasterStore, ISlaveStore |
57 | from canonical.launchpad.webapp.authorization import check_permission |
58 | from canonical.launchpad.webapp.interfaces import ( |
59 | IStoreSelector, MAIN_STORE, DEFAULT_FLAVOR) |
60 | @@ -160,12 +161,6 @@ |
61 | return sourcepackage.getSuiteSourcePackage(self.pocket) |
62 | |
63 | |
64 | -sm = getSiteManager() |
65 | -sm.registerAdapter(ProductTraversable) |
66 | -sm.registerAdapter(DistributionTraversable) |
67 | -sm.registerAdapter(DistroSeriesTraversable) |
68 | - |
69 | - |
70 | class LinkedBranchTraverser: |
71 | """Utility for traversing to objects that can have linked branches.""" |
72 | |
73 | @@ -259,6 +254,25 @@ |
74 | return None |
75 | return self._getBranchInNamespace(namespace_data, branch_name) |
76 | |
77 | + def getIdAndTrailingPath(self, path, from_slave=False): |
78 | + """See `IBranchLookup`. """ |
79 | + if from_slave: |
80 | + store = ISlaveStore(Branch) |
81 | + else: |
82 | + store = IMasterStore(Branch) |
83 | + prefixes = [] |
84 | + for first, second in iter_split(path[1:], '/'): |
85 | + prefixes.append(first) |
86 | + result = store.find( |
87 | + (Branch.id, Branch.unique_name), |
88 | + Branch.unique_name.is_in(prefixes), Branch.private == False).one() |
89 | + if result is None: |
90 | + return None, None |
91 | + else: |
92 | + branch_id, unique_name = result |
93 | + trailing = path[len(unique_name) + 1:] |
94 | + return branch_id, trailing |
95 | + |
96 | def _getBranchInNamespace(self, namespace_data, branch_name): |
97 | if namespace_data['product'] == '+junk': |
98 | return self._getPersonalBranch( |
99 | |
100 | === modified file 'lib/lp/code/model/tests/test_branchlookup.py' |
101 | --- lib/lp/code/model/tests/test_branchlookup.py 2009-08-28 06:39:38 +0000 |
102 | +++ lib/lp/code/model/tests/test_branchlookup.py 2009-10-20 21:56:15 +0000 |
103 | @@ -63,6 +63,49 @@ |
104 | self.assertEqual(branch, found_branch) |
105 | |
106 | |
107 | +class TestGetIdAndTrailingPath(TestCaseWithFactory): |
108 | + """Tests for `IBranchLookup.getIdAndTrailingPath`.""" |
109 | + |
110 | + layer = DatabaseFunctionalLayer |
111 | + |
112 | + def setUp(self): |
113 | + TestCaseWithFactory.setUp(self) |
114 | + self.branch_set = getUtility(IBranchLookup) |
115 | + |
116 | + def test_not_found(self): |
117 | + unused_name = self.factory.getUniqueString() |
118 | + result = self.branch_set.getIdAndTrailingPath('/' + unused_name) |
119 | + self.assertEqual((None, None), result) |
120 | + |
121 | + def test_junk(self): |
122 | + branch = self.factory.makePersonalBranch() |
123 | + result = self.branch_set.getIdAndTrailingPath('/' + branch.unique_name) |
124 | + self.assertEqual((branch.id, ''), result) |
125 | + |
126 | + def test_product(self): |
127 | + branch = self.factory.makeProductBranch() |
128 | + result = self.branch_set.getIdAndTrailingPath('/' + branch.unique_name) |
129 | + self.assertEqual((branch.id, ''), result) |
130 | + |
131 | + def test_source_package(self): |
132 | + branch = self.factory.makePackageBranch() |
133 | + result = self.branch_set.getIdAndTrailingPath('/' + branch.unique_name) |
134 | + self.assertEqual((branch.id, ''), result) |
135 | + |
136 | + def test_trailing_slash(self): |
137 | + branch = self.factory.makeAnyBranch() |
138 | + result = self.branch_set.getIdAndTrailingPath( |
139 | + '/' + branch.unique_name + '/') |
140 | + self.assertEqual((branch.id, '/'), result) |
141 | + |
142 | + def test_trailing_path(self): |
143 | + branch = self.factory.makeAnyBranch() |
144 | + path = self.factory.getUniqueString() |
145 | + result = self.branch_set.getIdAndTrailingPath( |
146 | + '/' + branch.unique_name + '/' + path) |
147 | + self.assertEqual((branch.id, '/' + path), result) |
148 | + |
149 | + |
150 | class TestGetByPath(TestCaseWithFactory): |
151 | """Test `IBranchLookup.getByLPPath`.""" |
152 | |
153 | |
154 | === modified file 'lib/lp/code/xmlrpc/codehosting.py' |
155 | --- lib/lp/code/xmlrpc/codehosting.py 2009-07-23 02:07:29 +0000 |
156 | +++ lib/lp/code/xmlrpc/codehosting.py 2009-10-20 21:56:15 +0000 |
157 | @@ -8,7 +8,6 @@ |
158 | 'BranchFileSystem', |
159 | 'BranchPuller', |
160 | 'datetime_from_tuple', |
161 | - 'iter_split', |
162 | ] |
163 | |
164 | |
165 | @@ -37,6 +36,7 @@ |
166 | from lp.registry.interfaces.person import IPersonSet, NoSuchPerson |
167 | from lp.registry.interfaces.product import NoSuchProduct |
168 | from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet |
169 | +from lp.services.utils import iter_split |
170 | from canonical.launchpad.validators import LaunchpadValidationError |
171 | from canonical.launchpad.webapp import LaunchpadXMLRPCView |
172 | from canonical.launchpad.webapp.authorization import check_permission |
173 | @@ -337,21 +337,3 @@ |
174 | raise faults.PathTranslationError(path) |
175 | return run_with_login(requester_id, translate_path) |
176 | |
177 | - |
178 | -def iter_split(string, splitter): |
179 | - """Iterate over ways to split 'string' in two with 'splitter'. |
180 | - |
181 | - If 'string' is empty, then yield nothing. Otherwise, yield tuples like |
182 | - ('a/b/c', ''), ('a/b', 'c'), ('a', 'b/c') for a string 'a/b/c' and a |
183 | - splitter '/'. |
184 | - |
185 | - The tuples are yielded such that the first tuple has everything in the |
186 | - first tuple. With each iteration, the first element gets smaller and the |
187 | - second gets larger. It stops iterating just before it would have to yield |
188 | - ('', 'a/b/c'). |
189 | - """ |
190 | - if string == '': |
191 | - return |
192 | - tokens = string.split(splitter) |
193 | - for i in reversed(range(1, len(tokens) + 1)): |
194 | - yield splitter.join(tokens[:i]), splitter.join(tokens[i:]) |
195 | |
196 | === modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py' |
197 | --- lib/lp/code/xmlrpc/tests/test_codehosting.py 2009-07-17 00:26:05 +0000 |
198 | +++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2009-10-20 21:56:15 +0000 |
199 | @@ -23,7 +23,7 @@ |
200 | from lp.code.interfaces.codehosting import ( |
201 | BRANCH_TRANSPORT, CONTROL_TRANSPORT) |
202 | from canonical.launchpad.interfaces.launchpad import ILaunchBag |
203 | -from lp.testing import TestCase, TestCaseWithFactory |
204 | +from lp.testing import TestCaseWithFactory |
205 | from lp.testing.factory import LaunchpadObjectFactory |
206 | from canonical.launchpad.webapp.interfaces import NotFoundError |
207 | from canonical.launchpad.xmlrpc import faults |
208 | @@ -36,7 +36,7 @@ |
209 | from lp.code.model.tests.test_branchpuller import AcquireBranchToPullTests |
210 | from lp.code.xmlrpc.codehosting import ( |
211 | BranchFileSystem, BranchPuller, LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES, |
212 | - iter_split, run_with_login) |
213 | + run_with_login) |
214 | |
215 | |
216 | UTC = pytz.timezone('UTC') |
217 | @@ -1109,23 +1109,6 @@ |
218 | trailing_path='.bzr') |
219 | |
220 | |
221 | -class TestIterateSplit(TestCase): |
222 | - """Tests for iter_split.""" |
223 | - |
224 | - def test_iter_split(self): |
225 | - # iter_split loops over each way of splitting a string in two using |
226 | - # the given splitter. |
227 | - self.assertEqual([('one', '')], list(iter_split('one', '/'))) |
228 | - self.assertEqual([], list(iter_split('', '/'))) |
229 | - self.assertEqual( |
230 | - [('one/two', ''), ('one', 'two')], |
231 | - list(iter_split('one/two', '/'))) |
232 | - self.assertEqual( |
233 | - [('one/two/three', ''), ('one/two', 'three'), |
234 | - ('one', 'two/three')], |
235 | - list(iter_split('one/two/three', '/'))) |
236 | - |
237 | - |
238 | class LaunchpadDatabaseFrontend: |
239 | """A 'frontend' to Launchpad's branch services. |
240 | |
241 | @@ -1182,7 +1165,5 @@ |
242 | 'layer': FunctionalLayer}), |
243 | ] |
244 | multiply_tests(puller_tests, scenarios, suite) |
245 | - suite.addTests( |
246 | - map(loader.loadTestsFromTestCase, |
247 | - [TestRunWithLogin, TestIterateSplit])) |
248 | + suite.addTests(loader.loadTestsFromTestCase(TestRunWithLogin)) |
249 | return suite |
250 | |
251 | === modified file 'lib/lp/codehosting/rewrite.py' |
252 | --- lib/lp/codehosting/rewrite.py 2009-07-23 23:42:47 +0000 |
253 | +++ lib/lp/codehosting/rewrite.py 2009-10-20 21:56:15 +0000 |
254 | @@ -8,11 +8,14 @@ |
255 | |
256 | from bzrlib import urlutils |
257 | |
258 | -from canonical.launchpad.interfaces import ISlaveStore |
259 | -from lp.code.model.branch import Branch |
260 | +from zope.component import getUtility |
261 | + |
262 | +from canonical.config import config |
263 | + |
264 | +from lp.code.interfaces.branchlookup import IBranchLookup |
265 | from lp.codehosting.vfs import branch_id_to_path |
266 | +from lp.services.utils import iter_split |
267 | |
268 | -from canonical.config import config |
269 | |
270 | __all__ = ['BranchRewriter'] |
271 | |
272 | @@ -31,7 +34,6 @@ |
273 | else: |
274 | self._now = _now |
275 | self.logger = logger |
276 | - self.store = ISlaveStore(Branch) |
277 | self._cache = {} |
278 | |
279 | def _codebrowse_url(self, path): |
280 | @@ -45,26 +47,19 @@ |
281 | In addition this method returns whether the answer can from the cache |
282 | or from the database. |
283 | """ |
284 | - parts = location[1:].split('/') |
285 | - prefixes = [] |
286 | - for i in range(1, len(parts) + 1): |
287 | - prefix = '/'.join(parts[:i]) |
288 | - if prefix in self._cache: |
289 | - branch_id, inserted_time = self._cache[prefix] |
290 | + for first, second in iter_split(location[1:], '/'): |
291 | + if first in self._cache: |
292 | + branch_id, inserted_time = self._cache[first] |
293 | if (self._now() < inserted_time + |
294 | config.codehosting.branch_rewrite_cache_lifetime): |
295 | - trailing = location[len(prefix) + 1:] |
296 | - return branch_id, trailing, "HIT" |
297 | - prefixes.append(prefix) |
298 | - result = self.store.find( |
299 | - (Branch.id, Branch.unique_name), |
300 | - Branch.unique_name.is_in(prefixes), Branch.private == False).one() |
301 | - if result is None: |
302 | + return branch_id, second, "HIT" |
303 | + branch_id, trailing = getUtility(IBranchLookup).getIdAndTrailingPath( |
304 | + location, from_slave=True) |
305 | + if branch_id is None: |
306 | return None, None, "MISS" |
307 | else: |
308 | - branch_id, unique_name = result |
309 | + unique_name = location[1:-len(trailing)] |
310 | self._cache[unique_name] = (branch_id, self._now()) |
311 | - trailing = location[len(unique_name) + 1:] |
312 | return branch_id, trailing, "MISS" |
313 | |
314 | def rewriteLine(self, resource_location): |
315 | |
316 | === modified file 'lib/lp/codehosting/tests/test_branchdistro.py' |
317 | --- lib/lp/codehosting/tests/test_branchdistro.py 2009-10-14 00:18:23 +0000 |
318 | +++ lib/lp/codehosting/tests/test_branchdistro.py 2009-10-20 21:56:15 +0000 |
319 | @@ -25,7 +25,7 @@ |
320 | import transaction |
321 | |
322 | from canonical.config import config |
323 | -from canonical.testing.layers import ZopelessAppServerLayer |
324 | +from canonical.testing.layers import LaunchpadZopelessLayer |
325 | from canonical.launchpad.scripts.logger import FakeLogger, QuietFakeLogger |
326 | |
327 | from lp.codehosting.branchdistro import DistroBrancher, switch_branches |
328 | @@ -104,11 +104,11 @@ |
329 | class TestDistroBrancher(TestCaseWithFactory): |
330 | """Tests for `DistroBrancher`.""" |
331 | |
332 | - layer = ZopelessAppServerLayer |
333 | + layer = LaunchpadZopelessLayer |
334 | |
335 | def setUp(self): |
336 | TestCaseWithFactory.setUp(self) |
337 | - self.useBzrBranches(real_server=True) |
338 | + self.useBzrBranches(real_server=True, direct_database=True) |
339 | |
340 | def makeOfficialPackageBranch(self, distroseries=None): |
341 | """Make an official package branch with an underlying bzr branch.""" |
342 | |
343 | === modified file 'lib/lp/codehosting/vfs/branchfs.py' |
344 | --- lib/lp/codehosting/vfs/branchfs.py 2009-07-17 18:46:25 +0000 |
345 | +++ lib/lp/codehosting/vfs/branchfs.py 2009-10-20 21:56:15 +0000 |
346 | @@ -53,6 +53,7 @@ |
347 | 'BadUrlSsh', |
348 | 'branch_id_to_path', |
349 | 'BranchPolicy', |
350 | + 'DirectDatabaseLaunchpadServer', |
351 | 'get_lp_server', |
352 | 'get_multi_server', |
353 | 'get_puller_server', |
354 | @@ -77,6 +78,7 @@ |
355 | from twisted.internet import defer |
356 | from twisted.python import failure |
357 | |
358 | +from zope.component import getUtility |
359 | from zope.interface import implements, Interface |
360 | |
361 | from lp.codehosting.vfs.branchfsclient import ( |
362 | @@ -87,6 +89,7 @@ |
363 | get_chrooted_transport, get_readonly_transport, TranslationError) |
364 | from canonical.config import config |
365 | from lp.code.enums import BranchType |
366 | +from lp.code.interfaces.branchlookup import IBranchLookup |
367 | from lp.code.interfaces.codehosting import ( |
368 | BRANCH_TRANSPORT, CONTROL_TRANSPORT, LAUNCHPAD_SERVICES) |
369 | from canonical.launchpad.xmlrpc import faults |
370 | @@ -180,20 +183,25 @@ |
371 | return get_multi_server(write_mirrored=True) |
372 | |
373 | |
374 | -def get_multi_server(write_hosted=False, write_mirrored=False): |
375 | +def get_multi_server(write_hosted=False, write_mirrored=False, |
376 | + direct_database=False): |
377 | """Get a server with access to both mirrored and hosted areas. |
378 | |
379 | - The server wraps up two `LaunchpadInternalServer`s. One of them points to |
380 | - the hosted branch area, the other points to the mirrored area. |
381 | + The server wraps up two `LaunchpadInternalServer`s or |
382 | + `DirectDatabaseLaunchpadServer`s. One server points to the hosted branch |
383 | + area and the other points to the mirrored area. |
384 | |
385 | Write permision defaults to False, but can be overridden. |
386 | + |
387 | :param write_hosted: if True, lp-hosted URLs are writeable. Otherwise, |
388 | they are read-only. |
389 | :param write_mirrored: if True, lp-mirrored URLs are writeable. |
390 | Otherwise, they are read-only. |
391 | + |
392 | + :param direct_database: if True, use a server implementation that talks |
393 | + directly to the database. If False, the default, use a server |
394 | + implementation that talks to the internal XML-RPC server. |
395 | """ |
396 | - proxy = xmlrpclib.ServerProxy(config.codehosting.branchfs_endpoint) |
397 | - branchfs_endpoint = BlockingProxy(proxy) |
398 | hosted_transport = get_chrooted_transport( |
399 | config.codehosting.hosted_branches_root, mkdir=True) |
400 | if not write_hosted: |
401 | @@ -202,10 +210,16 @@ |
402 | config.codehosting.mirrored_branches_root, mkdir=True) |
403 | if not write_mirrored: |
404 | mirrored_transport = get_readonly_transport(mirrored_transport) |
405 | - hosted_server = LaunchpadInternalServer( |
406 | - 'lp-hosted:///', branchfs_endpoint, hosted_transport) |
407 | - mirrored_server = LaunchpadInternalServer( |
408 | - 'lp-mirrored:///', branchfs_endpoint, mirrored_transport) |
409 | + if direct_database: |
410 | + make_server = DirectDatabaseLaunchpadServer |
411 | + else: |
412 | + proxy = xmlrpclib.ServerProxy(config.codehosting.branchfs_endpoint) |
413 | + branchfs_endpoint = BlockingProxy(proxy) |
414 | + def make_server(scheme, transport): |
415 | + return LaunchpadInternalServer( |
416 | + scheme, branchfs_endpoint, transport) |
417 | + hosted_server = make_server('lp-hosted:///', hosted_transport) |
418 | + mirrored_server = make_server('lp-mirrored:///', mirrored_transport) |
419 | return _MultiServer(hosted_server, mirrored_server) |
420 | |
421 | |
422 | @@ -427,6 +441,43 @@ |
423 | self.tearDown() |
424 | |
425 | |
426 | +class DirectDatabaseLaunchpadServer(AsyncVirtualServer): |
427 | + def __init__(self, scheme, branch_transport): |
428 | + AsyncVirtualServer.__init__(self, scheme) |
429 | + self._transport_dispatch = BranchTransportDispatch(branch_transport) |
430 | + |
431 | + def setUp(self): |
432 | + super(DirectDatabaseLaunchpadServer, self).setUp() |
433 | + try: |
434 | + self._transport_dispatch.base_transport.ensure_base() |
435 | + except TransportNotPossible: |
436 | + pass |
437 | + |
438 | + def destroy(self): |
439 | + """Delete the on-disk branches and tear down.""" |
440 | + self._transport_dispatch.base_transport.delete_tree('.') |
441 | + self.tearDown() |
442 | + |
443 | + def translateVirtualPath(self, virtual_url_fragment): |
444 | + """See `AsyncVirtualServer.translateVirtualPath`. |
445 | + |
446 | + This implementation connects to the database directly. |
447 | + """ |
448 | + deferred = defer.succeed( |
449 | + getUtility(IBranchLookup).getIdAndTrailingPath( |
450 | + virtual_url_fragment)) |
451 | + |
452 | + def process_result((branch_id, trailing)): |
453 | + if branch_id is None: |
454 | + raise NoSuchFile(virtual_url_fragment) |
455 | + else: |
456 | + return self._transport_dispatch.makeTransport( |
457 | + (BRANCH_TRANSPORT, dict(id=branch_id), trailing[1:])) |
458 | + |
459 | + deferred.addCallback(process_result) |
460 | + return deferred |
461 | + |
462 | + |
463 | class AsyncLaunchpadTransport(AsyncVirtualTransport): |
464 | """Virtual transport to implement the Launchpad VFS for branches. |
465 | |
466 | |
467 | === modified file 'lib/lp/codehosting/vfs/tests/test_branchfs.py' |
468 | --- lib/lp/codehosting/vfs/tests/test_branchfs.py 2009-06-25 04:06:00 +0000 |
469 | +++ lib/lp/codehosting/vfs/tests/test_branchfs.py 2009-10-20 21:56:15 +0000 |
470 | @@ -24,9 +24,9 @@ |
471 | from twisted.trial.unittest import TestCase as TrialTestCase |
472 | |
473 | from lp.codehosting.vfs.branchfs import ( |
474 | - AsyncLaunchpadTransport, branch_id_to_path, LaunchpadInternalServer, |
475 | - LaunchpadServer, BranchTransportDispatch, TransportDispatch, |
476 | - UnknownTransportType) |
477 | + AsyncLaunchpadTransport, BranchTransportDispatch, |
478 | + DirectDatabaseLaunchpadServer, LaunchpadInternalServer, LaunchpadServer, |
479 | + TransportDispatch, UnknownTransportType, branch_id_to_path) |
480 | from lp.codehosting.bzrutils import ensure_base |
481 | from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper |
482 | from lp.codehosting.sftp import FatLocalTransport |
483 | @@ -34,8 +34,8 @@ |
484 | from lp.code.enums import BranchType |
485 | from lp.code.interfaces.codehosting import ( |
486 | BRANCH_TRANSPORT, CONTROL_TRANSPORT) |
487 | -from lp.testing import TestCase |
488 | -from canonical.testing import TwistedLayer |
489 | +from lp.testing import TestCase, TestCaseWithFactory |
490 | +from canonical.testing import TwistedLayer, ZopelessDatabaseLayer |
491 | |
492 | |
493 | def branch_to_path(branch, add_slash=True): |
494 | @@ -293,18 +293,8 @@ |
495 | self.assertEqual('lp-%d:///' % id(self.server), self.server.get_url()) |
496 | |
497 | |
498 | -class TestLaunchpadInternalServer(MixinBaseLaunchpadServerTests, |
499 | - TrialTestCase, BzrTestCase): |
500 | - """Tests for the LaunchpadInternalServer, used by the puller and scanner. |
501 | - """ |
502 | - |
503 | - def setUp(self): |
504 | - BzrTestCase.setUp(self) |
505 | - MixinBaseLaunchpadServerTests.setUp(self) |
506 | - |
507 | - def getLaunchpadServer(self, authserver, user_id): |
508 | - return LaunchpadInternalServer( |
509 | - 'lp-test:///', XMLRPCWrapper(authserver), MemoryTransport()) |
510 | +class LaunchpadInternalServerTests: |
511 | + """Tests for the internal server classes, used by e.g. the scanner.""" |
512 | |
513 | def test_translate_branch_path(self): |
514 | branch = self.factory.makeAnyBranch() |
515 | @@ -314,7 +304,7 @@ |
516 | expected_transport, expected_path = dispatch |
517 | |
518 | deferred = self.server.translateVirtualPath( |
519 | - '%s/.bzr/README' % (branch.unique_name,)) |
520 | + '/%s/.bzr/README' % (branch.unique_name,)) |
521 | def check_branch_transport((transport, path)): |
522 | self.assertEqual(expected_path, path) |
523 | # Can't test for equality of transports, since URLs and object |
524 | @@ -348,6 +338,34 @@ |
525 | BzrDir.open_containing_from_transport, transport) |
526 | |
527 | |
528 | +class TestLaunchpadInternalServer(MixinBaseLaunchpadServerTests, |
529 | + TrialTestCase, BzrTestCase, |
530 | + LaunchpadInternalServerTests): |
531 | + """Tests for `LaunchpadInternalServer`, used by the puller and scanner.""" |
532 | + |
533 | + def setUp(self): |
534 | + BzrTestCase.setUp(self) |
535 | + MixinBaseLaunchpadServerTests.setUp(self) |
536 | + |
537 | + def getLaunchpadServer(self, authserver, user_id): |
538 | + return LaunchpadInternalServer( |
539 | + 'lp-test:///', XMLRPCWrapper(authserver), MemoryTransport()) |
540 | + |
541 | + |
542 | +class TestDirectDatabaseLaunchpadServer(TestCaseWithFactory, TrialTestCase, |
543 | + LaunchpadInternalServerTests): |
544 | + """Tests for `DirectDatabaseLaunchpadServer`.""" |
545 | + |
546 | + layer = ZopelessDatabaseLayer |
547 | + |
548 | + def setUp(self): |
549 | + super(TestDirectDatabaseLaunchpadServer, self).setUp() |
550 | + self.requester = self.factory.makePerson() |
551 | + self.server = DirectDatabaseLaunchpadServer( |
552 | + 'lp-test://', MemoryTransport()) |
553 | + |
554 | + |
555 | + |
556 | class TestAsyncVirtualTransport(TrialTestCase, TestCaseInTempDir): |
557 | """Tests for `AsyncVirtualTransport`.""" |
558 | |
559 | |
560 | === added file 'lib/lp/services/tests/test_utils.py' |
561 | --- lib/lp/services/tests/test_utils.py 1970-01-01 00:00:00 +0000 |
562 | +++ lib/lp/services/tests/test_utils.py 2009-10-20 21:56:15 +0000 |
563 | @@ -0,0 +1,32 @@ |
564 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
565 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
566 | + |
567 | +"""Module docstring goes here.""" |
568 | + |
569 | +__metaclass__ = type |
570 | + |
571 | +import unittest |
572 | + |
573 | +from lp.services.utils import iter_split |
574 | +from lp.testing import TestCase |
575 | + |
576 | + |
577 | +class TestIterateSplit(TestCase): |
578 | + """Tests for iter_split.""" |
579 | + |
580 | + def test_iter_split(self): |
581 | + # iter_split loops over each way of splitting a string in two using |
582 | + # the given splitter. |
583 | + self.assertEqual([('one', '')], list(iter_split('one', '/'))) |
584 | + self.assertEqual([], list(iter_split('', '/'))) |
585 | + self.assertEqual( |
586 | + [('one/two', ''), ('one', 'two')], |
587 | + list(iter_split('one/two', '/'))) |
588 | + self.assertEqual( |
589 | + [('one/two/three', ''), ('one/two', 'three'), |
590 | + ('one', 'two/three')], |
591 | + list(iter_split('one/two/three', '/'))) |
592 | + |
593 | + |
594 | +def test_suite(): |
595 | + return unittest.TestLoader().loadTestsFromName(__name__) |
596 | |
597 | === added file 'lib/lp/services/utils.py' |
598 | --- lib/lp/services/utils.py 1970-01-01 00:00:00 +0000 |
599 | +++ lib/lp/services/utils.py 2009-10-20 21:56:15 +0000 |
600 | @@ -0,0 +1,29 @@ |
601 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
602 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
603 | + |
604 | + |
605 | +"""Implementations of the XML-RPC APIs for codehosting.""" |
606 | + |
607 | +__metaclass__ = type |
608 | +__all__ = [ |
609 | + 'iter_split', |
610 | + ] |
611 | + |
612 | + |
613 | +def iter_split(string, splitter): |
614 | + """Iterate over ways to split 'string' in two with 'splitter'. |
615 | + |
616 | + If 'string' is empty, then yield nothing. Otherwise, yield tuples like |
617 | + ('a/b/c', ''), ('a/b', 'c'), ('a', 'b/c') for a string 'a/b/c' and a |
618 | + splitter '/'. |
619 | + |
620 | + The tuples are yielded such that the first tuple has everything in the |
621 | + first tuple. With each iteration, the first element gets smaller and the |
622 | + second gets larger. It stops iterating just before it would have to yield |
623 | + ('', 'a/b/c'). |
624 | + """ |
625 | + if string == '': |
626 | + return |
627 | + tokens = string.split(splitter) |
628 | + for i in reversed(range(1, len(tokens) + 1)): |
629 | + yield splitter.join(tokens[:i]), splitter.join(tokens[i:]) |
630 | |
631 | === modified file 'lib/lp/testing/__init__.py' |
632 | --- lib/lp/testing/__init__.py 2009-09-16 21:22:12 +0000 |
633 | +++ lib/lp/testing/__init__.py 2009-10-20 21:56:15 +0000 |
634 | @@ -572,7 +572,7 @@ |
635 | os.environ['BZR_HOME'] = os.getcwd() |
636 | self.addCleanup(restore_bzr_home) |
637 | |
638 | - def useBzrBranches(self, real_server=False): |
639 | + def useBzrBranches(self, real_server=False, direct_database=False): |
640 | """Prepare for using bzr branches. |
641 | |
642 | This sets up support for lp-hosted and lp-mirrored URLs, |
643 | @@ -586,7 +586,9 @@ |
644 | self.useTempBzrHome() |
645 | self.real_bzr_server = real_server |
646 | if real_server: |
647 | - server = get_multi_server(write_hosted=True, write_mirrored=True) |
648 | + server = get_multi_server( |
649 | + write_hosted=True, write_mirrored=True, |
650 | + direct_database=direct_database) |
651 | server.setUp() |
652 | self.addCleanup(server.destroy) |
653 | else: |
654 | |
655 | === modified file 'scripts/branch-distro.py' |
656 | --- scripts/branch-distro.py 2009-10-13 23:50:17 +0000 |
657 | +++ scripts/branch-distro.py 2009-10-20 21:56:15 +0000 |
658 | @@ -24,7 +24,8 @@ |
659 | if len(self.args) != 3: |
660 | self.parser.error("Wrong number of arguments.") |
661 | brancher = DistroBrancher.fromNames(self.logger, *self.args) |
662 | - server = get_multi_server(write_mirrored=True, write_hosted=True) |
663 | + server = get_multi_server( |
664 | + write_mirrored=True, write_hosted=True, direct_database=True) |
665 | server.setUp() |
666 | try: |
667 | if self.options.check: |
Hi,
This branch adds a way of translating branch paths from external to internal by direct communication with the database, and uses it for the branch-distro.py script. Tests on staging became about 50x faster as a result of this change.
Cheers,
mwh