Merge lp:~mwhudson/launchpad/in-memory-launchpad-server into lp:launchpad

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
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.

To post a comment you must log in.
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

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

Revision history for this message
Tim Penhey (thumper) wrote :

I think this is actually pretty good.

Some of the tests are missing comments/docstrings, but the combination of
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: