Merge lp:~thumper/launchpad/xmlrpc-tests into lp:launchpad/db-devel

Proposed by Tim Penhey
Status: Merged
Merged at revision: 9758
Proposed branch: lp:~thumper/launchpad/xmlrpc-tests
Merge into: lp:launchpad/db-devel
Diff against target: 366 lines (+79/-145)
3 files modified
Makefile (+2/-2)
lib/lp/code/xmlrpc/branch.py (+45/-15)
lib/lp/code/xmlrpc/tests/test_branch.py (+32/-128)
To merge this branch: bzr merge lp:~thumper/launchpad/xmlrpc-tests
Reviewer Review Type Date Requested Status
Graham Binns (community) release-critical Approve
Launchpad code reviewers Pending
Review via email: mp+34719@code.launchpad.net

Commit message

Correctly resolve lp path names to use the +branch alias.

Description of the change

This branch addresses issues found when QA testing the new lp:short-name for private branches.

When going through the XMLRPC resolve_lp_path tests, I seem to have forgotten the complete point of the change, which was to not look up the branch in most cases.

In order to return the http urls for branches accessed using lp:foo a branch lookup is still needed until the branch rewrite map can understand the aliases.

Many tests were removed as they are now pointless as the checking for the linked branch has been passed on to the code path that resolves the aliased name (an earlier branch).

tests:
  TestExpandURL

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

The Makefile change was a drive-by that I needed in order to run some interactive testing. The hosted_branches make target calls a broken script right now, and that was out of scope for this change.

Revision history for this message
Graham Binns (gmb) :
review: Approve (release-critical)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2010-09-05 20:29:40 +0000
3+++ Makefile 2010-09-07 05:51:45 +0000
4@@ -234,7 +234,7 @@
5 -r librarian,google-webservice
6 > ${LPCONFIG}-nohup.out 2>&1 &
7
8-run_all: check_schema inplace stop hosted_branches
9+run_all: check_schema inplace stop
10 $(RM) thread*.request
11 bin/run -r librarian,sftp,mailman,codebrowse,google-webservice,memcached \
12 -i $(LPCONFIG)
13@@ -248,7 +248,7 @@
14 stop_codebrowse:
15 $(PY) scripts/stop-loggerhead.py
16
17-run_codehosting: check_schema inplace stop hosted_branches
18+run_codehosting: check_schema inplace stop
19 $(RM) thread*.request
20 bin/run -r librarian,sftp,codebrowse -i $(LPCONFIG)
21
22
23=== modified file 'lib/lp/code/xmlrpc/branch.py'
24--- lib/lp/code/xmlrpc/branch.py 2010-09-02 21:56:09 +0000
25+++ lib/lp/code/xmlrpc/branch.py 2010-09-07 05:51:45 +0000
26@@ -15,6 +15,8 @@
27 ]
28
29
30+from xmlrpclib import Fault
31+
32 from bzrlib import urlutils
33 from zope.component import getUtility
34 from zope.interface import (
35@@ -214,9 +216,9 @@
36 'http': _compose_http_url,
37 }
38
39- def _getResultDict(self, branch, lp_path, suffix=None,
40- supported_schemes=None):
41- """Return a result dict with a list of URLs for the given branch.
42+ def _getUrlsForBranch(self, branch, lp_path, suffix=None,
43+ supported_schemes=None):
44+ """Return a list of URLs for the given branch.
45
46 :param branch: A Branch object.
47 :param lp_path: The path that was used to traverse to the branch.
48@@ -226,8 +228,8 @@
49 """
50 if branch.branch_type == BranchType.REMOTE:
51 if branch.url is None:
52- return faults.NoUrlForBranch(branch.unique_name)
53- return dict(urls=[branch.url])
54+ raise faults.NoUrlForBranch(branch.unique_name)
55+ return [branch.url]
56 else:
57 return self._getUniqueNameResultDict(
58 branch.unique_name, suffix, supported_schemes, lp_path)
59@@ -238,11 +240,8 @@
60 supported_schemes = SUPPORTED_SCHEMES
61 if path is None:
62 path = unique_name
63- result = dict(urls=[])
64- for scheme in supported_schemes:
65- result['urls'].append(
66- self.scheme_funcs[scheme](unique_name, path, suffix))
67- return result
68+ return [self.scheme_funcs[scheme](unique_name, path, suffix)
69+ for scheme in supported_schemes]
70
71 @return_fault
72 def _resolve_lp_path(self, path):
73@@ -254,11 +253,39 @@
74 strip_path = path.strip('/')
75 if strip_path == '':
76 raise faults.InvalidBranchIdentifier(path)
77- supported_schemes = SUPPORTED_SCHEMES
78+ supported_schemes = list(SUPPORTED_SCHEMES)
79 hot_products = [product.strip() for product
80 in config.codehosting.hot_products.split(',')]
81- if strip_path in hot_products:
82- supported_schemes = ['http']
83+ # If we have been given something that looks like a branch name, just
84+ # look that up.
85+ if strip_path.startswith('~'):
86+ urls = self._getBranchPaths(strip_path, supported_schemes)
87+ else:
88+ # We only check the hot product code when accessed through the
89+ # short name, so we can check it here.
90+ if strip_path in hot_products:
91+ supported_schemes = ['http']
92+ urls = []
93+ else:
94+ urls = [self.scheme_funcs['bzr+ssh'](None, strip_path, None)]
95+ supported_schemes.remove('bzr+ssh')
96+ # Try to look up the branch at that url and add alternative URLs.
97+ # This may well fail, and if it does, we just return the aliased
98+ # url.
99+ try:
100+ urls.extend(
101+ self._getBranchPaths(strip_path, supported_schemes))
102+ except Fault:
103+ pass
104+ return dict(urls=urls)
105+
106+ def _getBranchPaths(self, strip_path, supported_schemes):
107+ """Get the specific paths for a branch.
108+
109+ If the branch is not found, but it looks like a branch name, then we
110+ return a writable URL for it. If it doesn't look like a branch name a
111+ fault is raised.
112+ """
113 branch_set = getUtility(IBranchLookup)
114 try:
115 branch, suffix = branch_set.getByLPPath(strip_path)
116@@ -267,7 +294,9 @@
117 # resolve it anyway, treating the path like a branch's unique
118 # name. This lets people push new branches up to Launchpad using
119 # lp: URL syntax.
120- return self._getUniqueNameResultDict(strip_path)
121+ supported_schemes = ['bzr+ssh']
122+ return self._getUniqueNameResultDict(
123+ strip_path, supported_schemes=supported_schemes)
124 # XXX: JonathanLange 2009-03-21 bug=347728: All of this is repetitive
125 # and thus error prone. Alternatives are directly raising faults from
126 # the model code(blech) or some automated way of reraising as faults
127@@ -298,7 +327,8 @@
128 # E.g. 'project/trunk/filename.txt' the suffix is 'filename.txt'
129 # we want lp_path to be 'project/trunk'.
130 lp_path = lp_path[:-(len(suffix)+1)]
131- return self._getResultDict(branch, lp_path, suffix, supported_schemes)
132+ return self._getUrlsForBranch(
133+ branch, lp_path, suffix, supported_schemes)
134
135 def resolve_lp_path(self, path):
136 """See `IPublicCodehostingAPI`."""
137
138=== modified file 'lib/lp/code/xmlrpc/tests/test_branch.py'
139--- lib/lp/code/xmlrpc/tests/test_branch.py 2010-09-03 04:22:16 +0000
140+++ lib/lp/code/xmlrpc/tests/test_branch.py 2010-09-07 05:51:45 +0000
141@@ -59,7 +59,10 @@
142 if lp_path is None:
143 ssh_branch_path = public_branch_path
144 else:
145- ssh_branch_path = '%s/%s' % (BRANCH_ALIAS_PREFIX, lp_path)
146+ if lp_path.startswith('~'):
147+ ssh_branch_path = lp_path
148+ else:
149+ ssh_branch_path = '%s/%s' % (BRANCH_ALIAS_PREFIX, lp_path)
150 # This improves the error message if results happens to be a fault.
151 if isinstance(results, LaunchpadFault):
152 raise results
153@@ -70,6 +73,10 @@
154 else:
155 self.assertEqual('/' + ssh_branch_path, uri.path)
156
157+ def assertOnlyWritableResolves(self, lp_url_path):
158+ """Only the bzr+ssh url is returned."""
159+ self.assertResolves(lp_url_path, None, lp_url_path)
160+
161 def assertFault(self, lp_url_path, expected_fault):
162 """Trying to resolve lp_url_path raises the expected fault."""
163 api = PublicCodehostingAPI(None, None)
164@@ -122,54 +129,15 @@
165 lp_path='%s/%s' % (product.name, product.development_focus.name)
166 self.assertResolves(lp_path, trunk.unique_name, lp_path)
167
168- def test_product_doesnt_exist(self):
169- # Return a NoSuchProduct fault if the product doesn't exist.
170- self.assertFault(
171- 'doesntexist', faults.NoSuchProduct('doesntexist'))
172- self.assertFault(
173- 'doesntexist/trunk', faults.NoSuchProduct('doesntexist'))
174-
175- def test_project_group(self):
176- # Resolving lp:///project_group_name' should explain that project
177- # groups don't have default branches.
178- project_group = self.factory.makeProject()
179- fault = self.assertFault(
180- project_group.name, faults.CannotHaveLinkedBranch(project_group))
181- self.assertIn('project group', fault.faultString)
182-
183- def test_distro_name(self):
184- # Resolving lp:///distro_name' should explain that distributions don't
185- # have default branches.
186- distro = self.factory.makeDistribution()
187- self.assertFault(distro.name, faults.CannotHaveLinkedBranch(distro))
188-
189- def test_distroseries_name(self):
190- # Resolving lp:///distro/series' should explain that distribution
191- # series don't have default branches.
192- series = self.factory.makeDistroSeries()
193- self.assertFault(
194- '%s/%s' % (series.distribution.name, series.name),
195- faults.CannotHaveLinkedBranch(series))
196-
197- def test_invalid_product_name(self):
198- # If we get a string that cannot be a name for a product where we
199- # expect the name of a product, we should error appropriately.
200- invalid_name = '_' + self.factory.getUniqueString()
201- self.assertFault(
202- invalid_name,
203- faults.InvalidProductIdentifier(invalid_name))
204-
205- def test_invalid_product_name_non_ascii(self):
206- # lp:<non-ascii-string> returns InvalidProductIdentifier with the name
207- # escaped.
208- invalid_name = '_' + NON_ASCII_NAME
209- self.assertFault(
210- invalid_name,
211- faults.InvalidProductIdentifier(urlutils.escape(invalid_name)))
212+ def test_target_doesnt_exist(self):
213+ # The resolver doesn't care if the product exists or not.
214+ self.assertOnlyWritableResolves('doesntexist')
215+ self.assertOnlyWritableResolves('doesntexist/trunk')
216
217 def test_product_and_series(self):
218- # lp:product/series expands to the branch associated with the product
219- # series 'series' on 'product'.
220+ # lp:product/series expands to the writable alias for product/series
221+ # and to the branch associated with the product series 'series' on
222+ # 'product'.
223 product = self.factory.makeProduct()
224 branch = branch=self.factory.makeProductBranch(product=product)
225 series = self.factory.makeSeries(product=product, branch=branch)
226@@ -177,79 +145,23 @@
227 self.assertResolves(lp_path, branch.unique_name, lp_path)
228
229 def test_development_focus_has_no_branch(self):
230- # Return a NoLinkedBranch fault if the development focus has no branch
231- # associated with it.
232+ # A product with no trunk resolves to the writable alias.
233 product = self.factory.makeProduct()
234- self.assertEqual(None, product.development_focus.branch)
235- self.assertFault(product.name, faults.NoLinkedBranch(product))
236+ self.assertOnlyWritableResolves(product.name)
237
238 def test_series_has_no_branch(self):
239- # Return a NoLinkedBranch fault if the series has no branch
240- # associated with it.
241+ # A series with no branch resolves to the writable alias.
242 series = self.factory.makeSeries(branch=None)
243- self.assertFault(
244- '%s/%s' % (series.product.name, series.name),
245- faults.NoLinkedBranch(series))
246-
247- def test_no_such_product_series(self):
248- # Return a NoSuchProductSeries fault if there is no series of the
249- # given name associated with the product.
250- product = self.factory.makeProduct()
251- self.assertFault(
252- '%s/%s' % (product.name, "doesntexist"),
253- faults.NoSuchProductSeries("doesntexist", product))
254+ self.assertOnlyWritableResolves(
255+ '%s/%s' % (series.product.name, series.name))
256
257 def test_no_such_product_series_non_ascii(self):
258- # lp:product/<non-ascii-string> returns NoSuchProductSeries with the
259+ # lp:product/<non-ascii-string> resolves to the branch alias with the
260 # name escaped.
261 product = self.factory.makeProduct()
262- self.assertFault(
263- '%s/%s' % (product.name, NON_ASCII_NAME),
264- faults.NoSuchProductSeries(
265- urlutils.escape(NON_ASCII_NAME), product))
266-
267- def test_no_such_distro_series(self):
268- # Return a NoSuchDistroSeries fault if there is no series of the given
269- # name on that distribution.
270- distro = self.factory.makeDistribution()
271- self.assertFault(
272- '%s/doesntexist/whocares' % distro.name,
273- faults.NoSuchDistroSeries("doesntexist"))
274-
275- def test_no_such_distro_series_non_ascii(self):
276- # lp:distro/<non-ascii-string>/whatever returns NoSuchDistroSeries
277- # with the name escaped.
278- distro = self.factory.makeDistribution()
279- self.assertFault(
280- '%s/%s/whocares' % (distro.name, NON_ASCII_NAME),
281- faults.NoSuchDistroSeries(urlutils.escape(NON_ASCII_NAME)))
282-
283- def test_no_such_source_package(self):
284- # Return a NoSuchSourcePackageName fault if there is no source package
285- # of the given name.
286- distroseries = self.factory.makeDistroRelease()
287- distribution = distroseries.distribution
288- self.assertFault(
289- '%s/%s/doesntexist' % (distribution.name, distroseries.name),
290- faults.NoSuchSourcePackageName('doesntexist'))
291-
292- def test_no_such_source_package_non_ascii(self):
293- # lp:distro/series/<non-ascii-name> returns NoSuchSourcePackageName
294- # with the name escaped.
295- distroseries = self.factory.makeDistroRelease()
296- distribution = distroseries.distribution
297- self.assertFault(
298- '%s/%s/%s' % (
299- distribution.name, distroseries.name, NON_ASCII_NAME),
300- faults.NoSuchSourcePackageName(urlutils.escape(NON_ASCII_NAME)))
301-
302- def test_no_linked_branch_for_source_package(self):
303- # Return a NoLinkedBranch fault if there's no linked branch for the
304- # sourcepackage.
305- suite_sourcepackage = self.factory.makeSuiteSourcePackage()
306- self.assertFault(
307- suite_sourcepackage.path,
308- faults.NoLinkedBranch(suite_sourcepackage))
309+ lp_path = '%s/%s' % (product.name, NON_ASCII_NAME)
310+ escaped_name = urlutils.escape(lp_path)
311+ self.assertResolves(lp_path, None, escaped_name)
312
313 def test_branch(self):
314 # The unique name of a branch resolves to the unique name of the
315@@ -396,35 +308,27 @@
316 # private branch, and tests are currently running as an anonymous
317 # user.
318 unique_name = removeSecurityProxy(branch).unique_name
319- self.assertResolves(unique_name, unique_name)
320+ self.assertOnlyWritableResolves(unique_name)
321
322 def test_private_branch_on_series(self):
323- # We resolve invisible branches as if they don't exist. For
324- # references to product series, this means returning a
325- # NoLinkedBranch fault.
326+ # We resolve private linked branches using the writable alias.
327 #
328 # Removing security proxy because we need to be able to get at
329 # attributes of a private branch and these tests are running as an
330 # anonymous user.
331 branch = self.factory.makeAnyBranch(private=True)
332 series = self.factory.makeSeries(branch=branch)
333- self.assertFault(
334- '%s/%s' % (series.product.name, series.name),
335- faults.NoLinkedBranch(series))
336+ lp_path='%s/%s' % (series.product.name, series.name)
337+ self.assertOnlyWritableResolves(lp_path)
338
339 def test_private_branch_as_development_focus(self):
340- # We resolve invisible branches as if they don't exist.
341- #
342- # References to a product resolve to the branch associated with the
343- # development focus. If that branch is private, other views will
344- # indicate that there is no branch on the development focus. We do the
345- # same.
346+ # We resolve private linked branches using the writable alias.
347 product, trunk = self.makeProdutWithTrunk()
348 removeSecurityProxy(trunk).private = True
349- self.assertFault(product.name, faults.NoLinkedBranch(product))
350+ self.assertOnlyWritableResolves(product.name)
351
352 def test_private_branch_as_user(self):
353- # We resolve invisible branches as if they don't exist.
354+ # We resolve private branches as if they don't exist.
355 #
356 # References to a product resolve to the branch associated with the
357 # development focus. If that branch is private, other views will
358@@ -436,7 +340,7 @@
359 owner = self.factory.makePerson()
360 branch = self.factory.makeAnyBranch(owner=owner, private=True)
361 path = removeSecurityProxy(branch).unique_name
362- self.assertResolves(path, path)
363+ self.assertOnlyWritableResolves(path)
364
365 def test_remote_branch(self):
366 # For remote branches, return results that link to the actual remote

Subscribers

People subscribed via source and target branches

to status/vote changes: