Merge lp:~jml/launchpad/more-services into lp:launchpad
- more-services
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/more-services |
Merge into: | lp:launchpad |
Diff against target: |
800 lines (+208/-237) 15 files modified
lib/canonical/launchpad/scripts/__init__.py (+0/-3) lib/canonical/launchpad/xmlrpc/faults.py (+1/-35) lib/canonical/launchpad/xmlrpc/tests/test_authserver.py (+2/-8) lib/canonical/launchpad/xmlrpc/tests/test_faults.py (+0/-65) lib/lp/code/xmlrpc/tests/test_branch.py (+2/-1) lib/lp/code/xmlrpc/tests/test_codehosting.py (+20/-34) lib/lp/codehosting/sshserver/accesslog.py (+1/-15) lib/lp/codehosting/sshserver/auth.py (+1/-1) lib/lp/codehosting/vfs/branchfs.py (+3/-2) lib/lp/codehosting/vfs/branchfsclient.py (+0/-18) lib/lp/codehosting/vfs/tests/test_branchfsclient.py (+1/-54) lib/lp/services/twistedsupport/tests/test_xmlrpc.py (+90/-0) lib/lp/services/twistedsupport/xmlrpc.py (+27/-0) lib/lp/services/utils.py (+20/-1) lib/lp/services/xmlrpc.py (+40/-0) |
To merge this branch: | bzr merge lp:~jml/launchpad/more-services |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+23107@code.launchpad.net |
Commit message
Move Twisted fault support and the base LaunchpadFault to lp.services. Move 'synchronize' helper to lp.services.
Description of the change
This branch extracts some drive-by-cleanups from my uber-branch lp:~jml/launchpad/ssh-key-auth.
Specifically:
1. The higher-order function "synchronize" is moved to lp.services
2. The "trap_fault" helper for XMLRPC Twisted code is moved to lp.services.
3. The "LaunchpadFault" base class for all of our XMLRPC faults is moved to lp.services.xmlrpc
4. A XXX comment from 2005 is deleted
I know it's a bit of a grab-bag of changes, but they are all conceptually quite simple, so I hope it's an easy enough branch to review.
To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) : | # |
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/scripts/__init__.py' | |||
2 | --- lib/canonical/launchpad/scripts/__init__.py 2010-03-18 21:30:51 +0000 | |||
3 | +++ lib/canonical/launchpad/scripts/__init__.py 2010-04-10 13:58:26 +0000 | |||
4 | @@ -46,9 +46,6 @@ | |||
5 | 46 | # We should probably split out all the stuff in this directory that | 46 | # We should probably split out all the stuff in this directory that |
6 | 47 | # doesn't rely on Zope and migrate it to canonical/scripts. | 47 | # doesn't rely on Zope and migrate it to canonical/scripts. |
7 | 48 | 48 | ||
8 | 49 | # XXX SteveAlexander 2005-04-11: | ||
9 | 50 | # This is a total mess. I need to work out what this all means. | ||
10 | 51 | |||
11 | 52 | class NullItem: | 49 | class NullItem: |
12 | 53 | def __init__(self, context, handler, info, *argdata): | 50 | def __init__(self, context, handler, info, *argdata): |
13 | 54 | newcontext = GroupingContextDecorator(context) | 51 | newcontext = GroupingContextDecorator(context) |
14 | 55 | 52 | ||
15 | === modified file 'lib/canonical/launchpad/xmlrpc/faults.py' | |||
16 | --- lib/canonical/launchpad/xmlrpc/faults.py 2010-03-14 20:15:34 +0000 | |||
17 | +++ lib/canonical/launchpad/xmlrpc/faults.py 2010-04-10 13:58:26 +0000 | |||
18 | @@ -15,7 +15,6 @@ | |||
19 | 15 | 'BranchNameInUse', | 15 | 'BranchNameInUse', |
20 | 16 | 'BranchUniqueNameConflict', | 16 | 'BranchUniqueNameConflict', |
21 | 17 | 'CannotHaveLinkedBranch', | 17 | 'CannotHaveLinkedBranch', |
22 | 18 | 'check_fault', | ||
23 | 19 | 'FileBugGotProductAndDistro', | 18 | 'FileBugGotProductAndDistro', |
24 | 20 | 'FileBugMissingProductOrDistribution', | 19 | 'FileBugMissingProductOrDistribution', |
25 | 21 | 'InvalidBranchIdentifier', | 20 | 'InvalidBranchIdentifier', |
26 | @@ -42,41 +41,8 @@ | |||
27 | 42 | ] | 41 | ] |
28 | 43 | 42 | ||
29 | 44 | 43 | ||
30 | 45 | import xmlrpclib | ||
31 | 46 | |||
32 | 47 | |||
33 | 48 | from lp.registry.interfaces.projectgroup import IProjectGroup | 44 | from lp.registry.interfaces.projectgroup import IProjectGroup |
65 | 49 | 45 | from lp.services.xmlrpc import LaunchpadFault | |
35 | 50 | |||
36 | 51 | def check_fault(fault, *fault_classes): | ||
37 | 52 | """Check if 'fault's faultCode matches any of 'fault_classes'. | ||
38 | 53 | |||
39 | 54 | :param fault: An instance of `xmlrpclib.Fault`. | ||
40 | 55 | :param fault_classes: Any number of `LaunchpadFault` subclasses. | ||
41 | 56 | """ | ||
42 | 57 | for cls in fault_classes: | ||
43 | 58 | if fault.faultCode == cls.error_code: | ||
44 | 59 | return True | ||
45 | 60 | return False | ||
46 | 61 | |||
47 | 62 | |||
48 | 63 | class LaunchpadFault(xmlrpclib.Fault): | ||
49 | 64 | """Base class for a Launchpad XMLRPC fault. | ||
50 | 65 | |||
51 | 66 | Subclasses should define a unique error_code and a msg_template, | ||
52 | 67 | which will be interpolated with the given keyword arguments. | ||
53 | 68 | """ | ||
54 | 69 | |||
55 | 70 | error_code = None | ||
56 | 71 | msg_template = None | ||
57 | 72 | |||
58 | 73 | def __init__(self, **kw): | ||
59 | 74 | assert self.error_code is not None, ( | ||
60 | 75 | "Subclasses must define error_code.") | ||
61 | 76 | assert self.msg_template is not None, ( | ||
62 | 77 | "Subclasses must define msg_template.") | ||
63 | 78 | msg = self.msg_template % kw | ||
64 | 79 | xmlrpclib.Fault.__init__(self, self.error_code, msg) | ||
66 | 80 | 46 | ||
67 | 81 | 47 | ||
68 | 82 | class NoSuchProduct(LaunchpadFault): | 48 | class NoSuchProduct(LaunchpadFault): |
69 | 83 | 49 | ||
70 | === modified file 'lib/canonical/launchpad/xmlrpc/tests/test_authserver.py' | |||
71 | --- lib/canonical/launchpad/xmlrpc/tests/test_authserver.py 2009-06-25 05:30:52 +0000 | |||
72 | +++ lib/canonical/launchpad/xmlrpc/tests/test_authserver.py 2010-04-10 13:58:26 +0000 | |||
73 | @@ -16,6 +16,7 @@ | |||
74 | 16 | from canonical.launchpad.xmlrpc.authserver import AuthServerAPIView | 16 | from canonical.launchpad.xmlrpc.authserver import AuthServerAPIView |
75 | 17 | from canonical.testing.layers import DatabaseFunctionalLayer | 17 | from canonical.testing.layers import DatabaseFunctionalLayer |
76 | 18 | 18 | ||
77 | 19 | |||
78 | 19 | class GetUserAndSSHKeysTests(TestCaseWithFactory): | 20 | class GetUserAndSSHKeysTests(TestCaseWithFactory): |
79 | 20 | """Tests for the implementation of `IAuthServer.getUserAndSSHKeys`. | 21 | """Tests for the implementation of `IAuthServer.getUserAndSSHKeys`. |
80 | 21 | """ | 22 | """ |
81 | @@ -28,17 +29,10 @@ | |||
82 | 28 | self.authserver = AuthServerAPIView( | 29 | self.authserver = AuthServerAPIView( |
83 | 29 | private_root.authserver, TestRequest()) | 30 | private_root.authserver, TestRequest()) |
84 | 30 | 31 | ||
85 | 31 | def assertFaultEqual(self, expected_fault, observed_fault): | ||
86 | 32 | """Assert that `expected_fault` equals `observed_fault`.""" | ||
87 | 33 | self.assertIsInstance(observed_fault, faults.LaunchpadFault) | ||
88 | 34 | self.assertEqual(expected_fault.faultCode, observed_fault.faultCode) | ||
89 | 35 | self.assertEqual( | ||
90 | 36 | expected_fault.faultString, observed_fault.faultString) | ||
91 | 37 | |||
92 | 38 | def test_user_not_found(self): | 32 | def test_user_not_found(self): |
93 | 39 | # getUserAndSSHKeys returns the NoSuchPersonWithName fault if there is | 33 | # getUserAndSSHKeys returns the NoSuchPersonWithName fault if there is |
94 | 40 | # no Person of the given name. | 34 | # no Person of the given name. |
96 | 41 | self.assertFaultEqual( | 35 | self.assertEqual( |
97 | 42 | faults.NoSuchPersonWithName('no-one'), | 36 | faults.NoSuchPersonWithName('no-one'), |
98 | 43 | self.authserver.getUserAndSSHKeys('no-one')) | 37 | self.authserver.getUserAndSSHKeys('no-one')) |
99 | 44 | 38 | ||
100 | 45 | 39 | ||
101 | === removed file 'lib/canonical/launchpad/xmlrpc/tests/test_faults.py' | |||
102 | --- lib/canonical/launchpad/xmlrpc/tests/test_faults.py 2009-06-25 05:30:52 +0000 | |||
103 | +++ lib/canonical/launchpad/xmlrpc/tests/test_faults.py 1970-01-01 00:00:00 +0000 | |||
104 | @@ -1,65 +0,0 @@ | |||
105 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | ||
106 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
107 | 3 | |||
108 | 4 | """Tests for `canonical.launchpad.xmlrpc.faults`.""" | ||
109 | 5 | |||
110 | 6 | __metaclass__ = type | ||
111 | 7 | |||
112 | 8 | import unittest | ||
113 | 9 | |||
114 | 10 | from lp.testing import TestCase | ||
115 | 11 | from canonical.launchpad.xmlrpc import faults | ||
116 | 12 | |||
117 | 13 | |||
118 | 14 | class TestFaultOne(faults.LaunchpadFault): | ||
119 | 15 | """An arbitrary subclass of `LaunchpadFault`. | ||
120 | 16 | |||
121 | 17 | This class and `TestFaultTwo` are a pair of distinct `LaunchpadFault` | ||
122 | 18 | subclasses to use in tests. | ||
123 | 19 | """ | ||
124 | 20 | |||
125 | 21 | error_code = 1001 | ||
126 | 22 | msg_template = "Fault one." | ||
127 | 23 | |||
128 | 24 | |||
129 | 25 | class TestFaultTwo(faults.LaunchpadFault): | ||
130 | 26 | """Another arbitrary subclass of `LaunchpadFault`. | ||
131 | 27 | |||
132 | 28 | This class and `TestFaultOne` are a pair of distinct `LaunchpadFault` | ||
133 | 29 | subclasses to use in tests. | ||
134 | 30 | """ | ||
135 | 31 | |||
136 | 32 | error_code = 1002 | ||
137 | 33 | msg_template = "Fault two." | ||
138 | 34 | |||
139 | 35 | |||
140 | 36 | class TestTrapFault(TestCase): | ||
141 | 37 | """Tests for `check_fault`.""" | ||
142 | 38 | |||
143 | 39 | def test_wrong_fault(self): | ||
144 | 40 | # check_fault returns False if the passed fault does not have the code | ||
145 | 41 | # of one of the passed classes. | ||
146 | 42 | self.assertFalse( | ||
147 | 43 | faults.check_fault(TestFaultOne(), TestFaultTwo)) | ||
148 | 44 | |||
149 | 45 | def test_no_fault_classes(self): | ||
150 | 46 | # check_fault returns False if there are no passed classes. | ||
151 | 47 | self.assertFalse( | ||
152 | 48 | faults.check_fault(TestFaultOne())) | ||
153 | 49 | |||
154 | 50 | def test_matches(self): | ||
155 | 51 | # check_fault returns True if the passed fault has the code of the | ||
156 | 52 | # passed class. | ||
157 | 53 | self.assertTrue( | ||
158 | 54 | faults.check_fault(TestFaultOne(), TestFaultOne)) | ||
159 | 55 | |||
160 | 56 | def test_matches_one_of_set(self): | ||
161 | 57 | # check_fault returns True if the passed fault has the code of one of | ||
162 | 58 | # the passed classes. | ||
163 | 59 | self.assertTrue(faults.check_fault( | ||
164 | 60 | TestFaultOne(), TestFaultOne, TestFaultTwo)) | ||
165 | 61 | |||
166 | 62 | |||
167 | 63 | def test_suite(): | ||
168 | 64 | return unittest.TestLoader().loadTestsFromName(__name__) | ||
169 | 65 | |||
170 | 66 | 0 | ||
171 | === modified file 'lib/lp/code/xmlrpc/tests/test_branch.py' | |||
172 | --- lib/lp/code/xmlrpc/tests/test_branch.py 2010-02-22 06:16:47 +0000 | |||
173 | +++ lib/lp/code/xmlrpc/tests/test_branch.py 2010-04-10 13:58:26 +0000 | |||
174 | @@ -21,6 +21,7 @@ | |||
175 | 21 | from lp.testing import TestCaseWithFactory | 21 | from lp.testing import TestCaseWithFactory |
176 | 22 | from lazr.uri import URI | 22 | from lazr.uri import URI |
177 | 23 | from lp.code.xmlrpc.branch import PublicCodehostingAPI | 23 | from lp.code.xmlrpc.branch import PublicCodehostingAPI |
178 | 24 | from lp.services.xmlrpc import LaunchpadFault | ||
179 | 24 | from canonical.launchpad.xmlrpc import faults | 25 | from canonical.launchpad.xmlrpc import faults |
180 | 25 | from canonical.testing import DatabaseFunctionalLayer | 26 | from canonical.testing import DatabaseFunctionalLayer |
181 | 26 | 27 | ||
182 | @@ -62,7 +63,7 @@ | |||
183 | 62 | """Assert that `lp_url_path` path expands to `unique_name`.""" | 63 | """Assert that `lp_url_path` path expands to `unique_name`.""" |
184 | 63 | results = self.api.resolve_lp_path(lp_url_path) | 64 | results = self.api.resolve_lp_path(lp_url_path) |
185 | 64 | # This improves the error message if results happens to be a fault. | 65 | # This improves the error message if results happens to be a fault. |
187 | 65 | if isinstance(results, faults.LaunchpadFault): | 66 | if isinstance(results, LaunchpadFault): |
188 | 66 | raise results | 67 | raise results |
189 | 67 | for url in results['urls']: | 68 | for url in results['urls']: |
190 | 68 | self.assertEqual('/' + unique_name, URI(url).path) | 69 | self.assertEqual('/' + unique_name, URI(url).path) |
191 | 69 | 70 | ||
192 | === modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py' | |||
193 | --- lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-02-24 19:01:25 +0000 | |||
194 | +++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-04-10 13:58:26 +0000 | |||
195 | @@ -137,13 +137,6 @@ | |||
196 | 137 | self.branch_lookup = frontend.getBranchLookup() | 137 | self.branch_lookup = frontend.getBranchLookup() |
197 | 138 | self.getLastActivity = frontend.getLastActivity | 138 | self.getLastActivity = frontend.getLastActivity |
198 | 139 | 139 | ||
199 | 140 | def assertFaultEqual(self, expected_fault, observed_fault): | ||
200 | 141 | """Assert that `expected_fault` equals `observed_fault`.""" | ||
201 | 142 | self.assertIsInstance(observed_fault, faults.LaunchpadFault) | ||
202 | 143 | self.assertEqual(expected_fault.faultCode, observed_fault.faultCode) | ||
203 | 144 | self.assertEqual( | ||
204 | 145 | expected_fault.faultString, observed_fault.faultString) | ||
205 | 146 | |||
206 | 147 | def assertMirrorFailed(self, branch, failure_message, num_failures=1): | 140 | def assertMirrorFailed(self, branch, failure_message, num_failures=1): |
207 | 148 | """Assert that `branch` failed to mirror. | 141 | """Assert that `branch` failed to mirror. |
208 | 149 | 142 | ||
209 | @@ -204,7 +197,7 @@ | |||
210 | 204 | # exist. | 197 | # exist. |
211 | 205 | invalid_id = self.getUnusedBranchID() | 198 | invalid_id = self.getUnusedBranchID() |
212 | 206 | fault = self.storage.startMirroring(invalid_id) | 199 | fault = self.storage.startMirroring(invalid_id) |
214 | 207 | self.assertFaultEqual(faults.NoBranchWithID(invalid_id), fault) | 200 | self.assertEqual(faults.NoBranchWithID(invalid_id), fault) |
215 | 208 | 201 | ||
216 | 209 | def test_mirrorFailed(self): | 202 | def test_mirrorFailed(self): |
217 | 210 | branch = self.factory.makeAnyBranch() | 203 | branch = self.factory.makeAnyBranch() |
218 | @@ -220,7 +213,7 @@ | |||
219 | 220 | branch_id = self.getUnusedBranchID() | 213 | branch_id = self.getUnusedBranchID() |
220 | 221 | failure_message = self.factory.getUniqueString() | 214 | failure_message = self.factory.getUniqueString() |
221 | 222 | fault = self.storage.mirrorFailed(branch_id, failure_message) | 215 | fault = self.storage.mirrorFailed(branch_id, failure_message) |
223 | 223 | self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault) | 216 | self.assertEqual(faults.NoBranchWithID(branch_id), fault) |
224 | 224 | 217 | ||
225 | 225 | def test_mirrorComplete(self): | 218 | def test_mirrorComplete(self): |
226 | 226 | # mirrorComplete marks the branch as having been successfully | 219 | # mirrorComplete marks the branch as having been successfully |
227 | @@ -240,7 +233,7 @@ | |||
228 | 240 | branch_id = self.getUnusedBranchID() | 233 | branch_id = self.getUnusedBranchID() |
229 | 241 | fault = self.storage.mirrorComplete( | 234 | fault = self.storage.mirrorComplete( |
230 | 242 | branch_id, self.factory.getUniqueString()) | 235 | branch_id, self.factory.getUniqueString()) |
232 | 243 | self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault) | 236 | self.assertEqual(faults.NoBranchWithID(branch_id), fault) |
233 | 244 | 237 | ||
234 | 245 | def test_mirrorComplete_resets_failure_count(self): | 238 | def test_mirrorComplete_resets_failure_count(self): |
235 | 246 | # mirrorComplete marks the branch as successfully mirrored and removes | 239 | # mirrorComplete marks the branch as successfully mirrored and removes |
236 | @@ -388,7 +381,7 @@ | |||
237 | 388 | stacked_branch = self.factory.makeAnyBranch() | 381 | stacked_branch = self.factory.makeAnyBranch() |
238 | 389 | url = self.factory.getUniqueURL() | 382 | url = self.factory.getUniqueURL() |
239 | 390 | fault = self.storage.setStackedOn(stacked_branch.id, url) | 383 | fault = self.storage.setStackedOn(stacked_branch.id, url) |
241 | 391 | self.assertFaultEqual(faults.NoSuchBranch(url), fault) | 384 | self.assertEqual(faults.NoSuchBranch(url), fault) |
242 | 392 | 385 | ||
243 | 393 | def test_setStackedOnNoBranchWithID(self): | 386 | def test_setStackedOnNoBranchWithID(self): |
244 | 394 | # If setStackedOn is called for a branch that doesn't exist, it will | 387 | # If setStackedOn is called for a branch that doesn't exist, it will |
245 | @@ -397,7 +390,7 @@ | |||
246 | 397 | branch_type=BranchType.MIRRORED) | 390 | branch_type=BranchType.MIRRORED) |
247 | 398 | branch_id = self.getUnusedBranchID() | 391 | branch_id = self.getUnusedBranchID() |
248 | 399 | fault = self.storage.setStackedOn(branch_id, stacked_on_branch.url) | 392 | fault = self.storage.setStackedOn(branch_id, stacked_on_branch.url) |
250 | 400 | self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault) | 393 | self.assertEqual(faults.NoBranchWithID(branch_id), fault) |
251 | 401 | 394 | ||
252 | 402 | 395 | ||
253 | 403 | class AcquireBranchToPullTestsViaEndpoint(TestCaseWithFactory, | 396 | class AcquireBranchToPullTestsViaEndpoint(TestCaseWithFactory, |
254 | @@ -499,13 +492,6 @@ | |||
255 | 499 | self.factory = frontend.getLaunchpadObjectFactory() | 492 | self.factory = frontend.getLaunchpadObjectFactory() |
256 | 500 | self.branch_lookup = frontend.getBranchLookup() | 493 | self.branch_lookup = frontend.getBranchLookup() |
257 | 501 | 494 | ||
258 | 502 | def assertFaultEqual(self, expected_fault, observed_fault): | ||
259 | 503 | """Assert that `expected_fault` equals `observed_fault`.""" | ||
260 | 504 | self.assertIsInstance(observed_fault, faults.LaunchpadFault) | ||
261 | 505 | self.assertEqual(expected_fault.faultCode, observed_fault.faultCode) | ||
262 | 506 | self.assertEqual( | ||
263 | 507 | expected_fault.faultString, observed_fault.faultString) | ||
264 | 508 | |||
265 | 509 | def test_createBranch(self): | 495 | def test_createBranch(self): |
266 | 510 | # createBranch creates a branch with the supplied details and the | 496 | # createBranch creates a branch with the supplied details and the |
267 | 511 | # caller as registrant. | 497 | # caller as registrant. |
268 | @@ -527,7 +513,7 @@ | |||
269 | 527 | path = escape(u'invalid') | 513 | path = escape(u'invalid') |
270 | 528 | fault = self.branchfs.createBranch(requester.id, path) | 514 | fault = self.branchfs.createBranch(requester.id, path) |
271 | 529 | login(ANONYMOUS) | 515 | login(ANONYMOUS) |
273 | 530 | self.assertFaultEqual(faults.InvalidPath(path), fault) | 516 | self.assertEqual(faults.InvalidPath(path), fault) |
274 | 531 | 517 | ||
275 | 532 | def test_createBranch_junk(self): | 518 | def test_createBranch_junk(self): |
276 | 533 | # createBranch can create +junk branches. | 519 | # createBranch can create +junk branches. |
277 | @@ -565,7 +551,7 @@ | |||
278 | 565 | message = "Project 'no-such-product' does not exist." | 551 | message = "Project 'no-such-product' does not exist." |
279 | 566 | fault = self.branchfs.createBranch( | 552 | fault = self.branchfs.createBranch( |
280 | 567 | owner.id, escape('/~%s/no-such-product/%s' % (owner.name, name))) | 553 | owner.id, escape('/~%s/no-such-product/%s' % (owner.name, name))) |
282 | 568 | self.assertFaultEqual(faults.NotFound(message), fault) | 554 | self.assertEqual(faults.NotFound(message), fault) |
283 | 569 | 555 | ||
284 | 570 | def test_createBranch_other_user(self): | 556 | def test_createBranch_other_user(self): |
285 | 571 | # Creating a branch under another user's directory fails. | 557 | # Creating a branch under another user's directory fails. |
286 | @@ -578,7 +564,7 @@ | |||
287 | 578 | fault = self.branchfs.createBranch( | 564 | fault = self.branchfs.createBranch( |
288 | 579 | creator.id, | 565 | creator.id, |
289 | 580 | escape('/~%s/%s/%s' % (other_person.name, product.name, name))) | 566 | escape('/~%s/%s/%s' % (other_person.name, product.name, name))) |
291 | 581 | self.assertFaultEqual(faults.PermissionDenied(message), fault) | 567 | self.assertEqual(faults.PermissionDenied(message), fault) |
292 | 582 | 568 | ||
293 | 583 | def test_createBranch_bad_name(self): | 569 | def test_createBranch_bad_name(self): |
294 | 584 | # Creating a branch with an invalid name fails. | 570 | # Creating a branch with an invalid name fails. |
295 | @@ -590,7 +576,7 @@ | |||
296 | 590 | fault = self.branchfs.createBranch( | 576 | fault = self.branchfs.createBranch( |
297 | 591 | owner.id, escape( | 577 | owner.id, escape( |
298 | 592 | '/~%s/%s/%s' % (owner.name, product.name, invalid_name))) | 578 | '/~%s/%s/%s' % (owner.name, product.name, invalid_name))) |
300 | 593 | self.assertFaultEqual(faults.PermissionDenied(message), fault) | 579 | self.assertEqual(faults.PermissionDenied(message), fault) |
301 | 594 | 580 | ||
302 | 595 | def test_createBranch_unicode_name(self): | 581 | def test_createBranch_unicode_name(self): |
303 | 596 | # Creating a branch with an invalid name fails. | 582 | # Creating a branch with an invalid name fails. |
304 | @@ -603,7 +589,7 @@ | |||
305 | 603 | fault = self.branchfs.createBranch( | 589 | fault = self.branchfs.createBranch( |
306 | 604 | owner.id, escape( | 590 | owner.id, escape( |
307 | 605 | '/~%s/%s/%s' % (owner.name, product.name, invalid_name))) | 591 | '/~%s/%s/%s' % (owner.name, product.name, invalid_name))) |
309 | 606 | self.assertFaultEqual( | 592 | self.assertEqual( |
310 | 607 | faults.PermissionDenied(message), fault) | 593 | faults.PermissionDenied(message), fault) |
311 | 608 | 594 | ||
312 | 609 | def test_createBranch_bad_user(self): | 595 | def test_createBranch_bad_user(self): |
313 | @@ -614,7 +600,7 @@ | |||
314 | 614 | message = "User/team 'no-one' does not exist." | 600 | message = "User/team 'no-one' does not exist." |
315 | 615 | fault = self.branchfs.createBranch( | 601 | fault = self.branchfs.createBranch( |
316 | 616 | owner.id, escape('/~no-one/%s/%s' % (product.name, name))) | 602 | owner.id, escape('/~no-one/%s/%s' % (product.name, name))) |
318 | 617 | self.assertFaultEqual(faults.NotFound(message), fault) | 603 | self.assertEqual(faults.NotFound(message), fault) |
319 | 618 | 604 | ||
320 | 619 | def test_createBranch_bad_user_bad_product(self): | 605 | def test_createBranch_bad_user_bad_product(self): |
321 | 620 | # If both the user and the product are not found, then the missing | 606 | # If both the user and the product are not found, then the missing |
322 | @@ -625,7 +611,7 @@ | |||
323 | 625 | message = "User/team 'no-one' does not exist." | 611 | message = "User/team 'no-one' does not exist." |
324 | 626 | fault = self.branchfs.createBranch( | 612 | fault = self.branchfs.createBranch( |
325 | 627 | owner.id, escape('/~no-one/no-product/%s' % (name,))) | 613 | owner.id, escape('/~no-one/no-product/%s' % (name,))) |
327 | 628 | self.assertFaultEqual(faults.NotFound(message), fault) | 614 | self.assertEqual(faults.NotFound(message), fault) |
328 | 629 | 615 | ||
329 | 630 | def test_createBranch_not_branch(self): | 616 | def test_createBranch_not_branch(self): |
330 | 631 | # Trying to create a branch at a path that's not valid for branches | 617 | # Trying to create a branch at a path that's not valid for branches |
331 | @@ -634,7 +620,7 @@ | |||
332 | 634 | path = escape('/~%s' % owner.name) | 620 | path = escape('/~%s' % owner.name) |
333 | 635 | fault = self.branchfs.createBranch(owner.id, path) | 621 | fault = self.branchfs.createBranch(owner.id, path) |
334 | 636 | message = "Cannot create branch at '%s'" % path | 622 | message = "Cannot create branch at '%s'" % path |
336 | 637 | self.assertFaultEqual(faults.PermissionDenied(message), fault) | 623 | self.assertEqual(faults.PermissionDenied(message), fault) |
337 | 638 | 624 | ||
338 | 639 | def test_createBranch_source_package(self): | 625 | def test_createBranch_source_package(self): |
339 | 640 | # createBranch can take the path to a source package branch and create | 626 | # createBranch can take the path to a source package branch and create |
340 | @@ -671,7 +657,7 @@ | |||
341 | 671 | branch_name) | 657 | branch_name) |
342 | 672 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) | 658 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) |
343 | 673 | message = "No such distribution: 'ningnangnong'." | 659 | message = "No such distribution: 'ningnangnong'." |
345 | 674 | self.assertFaultEqual(faults.NotFound(message), fault) | 660 | self.assertEqual(faults.NotFound(message), fault) |
346 | 675 | 661 | ||
347 | 676 | def test_createBranch_invalid_distroseries(self): | 662 | def test_createBranch_invalid_distroseries(self): |
348 | 677 | # If createBranch is called with the path to a non-existent | 663 | # If createBranch is called with the path to a non-existent |
349 | @@ -685,7 +671,7 @@ | |||
350 | 685 | branch_name) | 671 | branch_name) |
351 | 686 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) | 672 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) |
352 | 687 | message = "No such distribution series: 'ningnangnong'." | 673 | message = "No such distribution series: 'ningnangnong'." |
354 | 688 | self.assertFaultEqual(faults.NotFound(message), fault) | 674 | self.assertEqual(faults.NotFound(message), fault) |
355 | 689 | 675 | ||
356 | 690 | def test_createBranch_invalid_sourcepackagename(self): | 676 | def test_createBranch_invalid_sourcepackagename(self): |
357 | 691 | # If createBranch is called with the path to an invalid source | 677 | # If createBranch is called with the path to an invalid source |
358 | @@ -698,7 +684,7 @@ | |||
359 | 698 | branch_name) | 684 | branch_name) |
360 | 699 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) | 685 | fault = self.branchfs.createBranch(owner.id, escape(unique_name)) |
361 | 700 | message = "No such source package: 'ningnangnong'." | 686 | message = "No such source package: 'ningnangnong'." |
363 | 701 | self.assertFaultEqual(faults.NotFound(message), fault) | 687 | self.assertEqual(faults.NotFound(message), fault) |
364 | 702 | 688 | ||
365 | 703 | def test_initialMirrorRequest(self): | 689 | def test_initialMirrorRequest(self): |
366 | 704 | # The default 'next_mirror_time' for a newly created hosted branch | 690 | # The default 'next_mirror_time' for a newly created hosted branch |
367 | @@ -727,21 +713,21 @@ | |||
368 | 727 | def assertCannotTranslate(self, requester, path): | 713 | def assertCannotTranslate(self, requester, path): |
369 | 728 | """Assert that we cannot translate 'path'.""" | 714 | """Assert that we cannot translate 'path'.""" |
370 | 729 | fault = self.branchfs.translatePath(requester.id, path) | 715 | fault = self.branchfs.translatePath(requester.id, path) |
372 | 730 | self.assertFaultEqual(faults.PathTranslationError(path), fault) | 716 | self.assertEqual(faults.PathTranslationError(path), fault) |
373 | 731 | 717 | ||
374 | 732 | def assertNotFound(self, requester, path): | 718 | def assertNotFound(self, requester, path): |
375 | 733 | """Assert that the given path cannot be found.""" | 719 | """Assert that the given path cannot be found.""" |
376 | 734 | if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]: | 720 | if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]: |
377 | 735 | requester = requester.id | 721 | requester = requester.id |
378 | 736 | fault = self.branchfs.translatePath(requester, path) | 722 | fault = self.branchfs.translatePath(requester, path) |
380 | 737 | self.assertFaultEqual(faults.PathTranslationError(path), fault) | 723 | self.assertEqual(faults.PathTranslationError(path), fault) |
381 | 738 | 724 | ||
382 | 739 | def assertPermissionDenied(self, requester, path): | 725 | def assertPermissionDenied(self, requester, path): |
383 | 740 | """Assert that looking at the given path gives permission denied.""" | 726 | """Assert that looking at the given path gives permission denied.""" |
384 | 741 | if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]: | 727 | if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]: |
385 | 742 | requester = requester.id | 728 | requester = requester.id |
386 | 743 | fault = self.branchfs.translatePath(requester, path) | 729 | fault = self.branchfs.translatePath(requester, path) |
388 | 744 | self.assertFaultEqual(faults.PermissionDenied(), fault) | 730 | self.assertEqual(faults.PermissionDenied(), fault) |
389 | 745 | 731 | ||
390 | 746 | def _makeProductWithDevFocus(self, private=False): | 732 | def _makeProductWithDevFocus(self, private=False): |
391 | 747 | """Make a stacking-enabled product with a development focus. | 733 | """Make a stacking-enabled product with a development focus. |
392 | @@ -769,7 +755,7 @@ | |||
393 | 769 | requester = self.factory.makePerson() | 755 | requester = self.factory.makePerson() |
394 | 770 | path = escape(u'invalid') | 756 | path = escape(u'invalid') |
395 | 771 | fault = self.branchfs.translatePath(requester.id, path) | 757 | fault = self.branchfs.translatePath(requester.id, path) |
397 | 772 | self.assertFaultEqual(faults.InvalidPath(path), fault) | 758 | self.assertEqual(faults.InvalidPath(path), fault) |
398 | 773 | 759 | ||
399 | 774 | def test_translatePath_branch(self): | 760 | def test_translatePath_branch(self): |
400 | 775 | requester = self.factory.makePerson() | 761 | requester = self.factory.makePerson() |
401 | 776 | 762 | ||
402 | === modified file 'lib/lp/codehosting/sshserver/accesslog.py' | |||
403 | --- lib/lp/codehosting/sshserver/accesslog.py 2010-03-19 10:43:51 +0000 | |||
404 | +++ lib/lp/codehosting/sshserver/accesslog.py 2010-04-10 13:58:26 +0000 | |||
405 | @@ -29,24 +29,10 @@ | |||
406 | 29 | 29 | ||
407 | 30 | from canonical.config import config | 30 | from canonical.config import config |
408 | 31 | from canonical.launchpad.scripts import WatchedFileHandler | 31 | from canonical.launchpad.scripts import WatchedFileHandler |
409 | 32 | from lp.services.utils import synchronize | ||
410 | 32 | from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting | 33 | from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting |
411 | 33 | 34 | ||
412 | 34 | 35 | ||
413 | 35 | def synchronize(source, target, add, remove): | ||
414 | 36 | """Update 'source' to match 'target' using 'add' and 'remove'. | ||
415 | 37 | |||
416 | 38 | Changes the container 'source' so that it equals 'target', calling 'add' | ||
417 | 39 | with any object in 'target' not in 'source' and 'remove' with any object | ||
418 | 40 | not in 'target' but in 'source'. | ||
419 | 41 | """ | ||
420 | 42 | need_to_add = [obj for obj in target if obj not in source] | ||
421 | 43 | need_to_remove = [obj for obj in source if obj not in target] | ||
422 | 44 | for obj in need_to_add: | ||
423 | 45 | add(obj) | ||
424 | 46 | for obj in need_to_remove: | ||
425 | 47 | remove(obj) | ||
426 | 48 | |||
427 | 49 | |||
428 | 50 | class LoggingManager: | 36 | class LoggingManager: |
429 | 51 | """Class for managing codehosting logging.""" | 37 | """Class for managing codehosting logging.""" |
430 | 52 | 38 | ||
431 | 53 | 39 | ||
432 | === modified file 'lib/lp/codehosting/sshserver/auth.py' | |||
433 | --- lib/lp/codehosting/sshserver/auth.py 2009-06-25 04:06:00 +0000 | |||
434 | +++ lib/lp/codehosting/sshserver/auth.py 2010-04-10 13:58:26 +0000 | |||
435 | @@ -42,7 +42,7 @@ | |||
436 | 42 | from lp.codehosting.sshserver import accesslog | 42 | from lp.codehosting.sshserver import accesslog |
437 | 43 | from lp.codehosting.sshserver.session import ( | 43 | from lp.codehosting.sshserver.session import ( |
438 | 44 | launch_smart_server, PatchedSSHSession) | 44 | launch_smart_server, PatchedSSHSession) |
440 | 45 | from lp.codehosting.vfs.branchfsclient import trap_fault | 45 | from lp.services.twistedsupport.xmlrpc import trap_fault |
441 | 46 | from canonical.config import config | 46 | from canonical.config import config |
442 | 47 | from canonical.launchpad.xmlrpc import faults | 47 | from canonical.launchpad.xmlrpc import faults |
443 | 48 | 48 | ||
444 | 49 | 49 | ||
445 | === modified file 'lib/lp/codehosting/vfs/branchfs.py' | |||
446 | --- lib/lp/codehosting/vfs/branchfs.py 2010-03-23 03:35:13 +0000 | |||
447 | +++ lib/lp/codehosting/vfs/branchfs.py 2010-04-10 13:58:26 +0000 | |||
448 | @@ -83,16 +83,17 @@ | |||
449 | 83 | from zope.interface import implements, Interface | 83 | from zope.interface import implements, Interface |
450 | 84 | 84 | ||
451 | 85 | from lp.codehosting.vfs.branchfsclient import ( | 85 | from lp.codehosting.vfs.branchfsclient import ( |
453 | 86 | BlockingProxy, BranchFileSystemClient, trap_fault) | 86 | BlockingProxy, BranchFileSystemClient) |
454 | 87 | from lp.codehosting.vfs.transport import ( | 87 | from lp.codehosting.vfs.transport import ( |
455 | 88 | AsyncVirtualServer, AsyncVirtualTransport, _MultiServer, | 88 | AsyncVirtualServer, AsyncVirtualTransport, _MultiServer, |
456 | 89 | get_chrooted_transport, get_readonly_transport, TranslationError) | 89 | get_chrooted_transport, get_readonly_transport, TranslationError) |
457 | 90 | from canonical.config import config | 90 | from canonical.config import config |
458 | 91 | from canonical.launchpad.xmlrpc import faults | ||
459 | 91 | from lp.code.enums import BranchType | 92 | from lp.code.enums import BranchType |
460 | 92 | from lp.code.interfaces.branchlookup import IBranchLookup | 93 | from lp.code.interfaces.branchlookup import IBranchLookup |
461 | 93 | from lp.code.interfaces.codehosting import ( | 94 | from lp.code.interfaces.codehosting import ( |
462 | 94 | BRANCH_TRANSPORT, CONTROL_TRANSPORT, LAUNCHPAD_SERVICES) | 95 | BRANCH_TRANSPORT, CONTROL_TRANSPORT, LAUNCHPAD_SERVICES) |
464 | 95 | from canonical.launchpad.xmlrpc import faults | 96 | from lp.services.twistedsupport.xmlrpc import trap_fault |
465 | 96 | 97 | ||
466 | 97 | 98 | ||
467 | 98 | class BadUrl(Exception): | 99 | class BadUrl(Exception): |
468 | 99 | 100 | ||
469 | === modified file 'lib/lp/codehosting/vfs/branchfsclient.py' | |||
470 | --- lib/lp/codehosting/vfs/branchfsclient.py 2010-03-23 02:34:35 +0000 | |||
471 | +++ lib/lp/codehosting/vfs/branchfsclient.py 2010-04-10 13:58:26 +0000 | |||
472 | @@ -11,13 +11,11 @@ | |||
473 | 11 | 'BlockingProxy', | 11 | 'BlockingProxy', |
474 | 12 | 'BranchFileSystemClient', | 12 | 'BranchFileSystemClient', |
475 | 13 | 'NotInCache', | 13 | 'NotInCache', |
476 | 14 | 'trap_fault', | ||
477 | 15 | ] | 14 | ] |
478 | 16 | 15 | ||
479 | 17 | import time | 16 | import time |
480 | 18 | 17 | ||
481 | 19 | from twisted.internet import defer | 18 | from twisted.internet import defer |
482 | 20 | from twisted.web.xmlrpc import Fault | ||
483 | 21 | 19 | ||
484 | 22 | from lp.code.interfaces.codehosting import BRANCH_TRANSPORT | 20 | from lp.code.interfaces.codehosting import BRANCH_TRANSPORT |
485 | 23 | 21 | ||
486 | @@ -136,19 +134,3 @@ | |||
487 | 136 | 'translatePath', self._user_id, path) | 134 | 'translatePath', self._user_id, path) |
488 | 137 | deferred.addCallback(self._addToCache, path) | 135 | deferred.addCallback(self._addToCache, path) |
489 | 138 | return deferred | 136 | return deferred |
490 | 139 | |||
491 | 140 | |||
492 | 141 | def trap_fault(failure, *fault_classes): | ||
493 | 142 | """Trap a fault, based on fault code. | ||
494 | 143 | |||
495 | 144 | :param failure: A Twisted L{Failure}. | ||
496 | 145 | :param *fault_codes: `LaunchpadFault` subclasses. | ||
497 | 146 | :raise Failure: if 'failure' is not a Fault failure, or if the fault code | ||
498 | 147 | does not match the given codes. | ||
499 | 148 | :return: The Fault if it matches one of the codes. | ||
500 | 149 | """ | ||
501 | 150 | failure.trap(Fault) | ||
502 | 151 | fault = failure.value | ||
503 | 152 | if fault.faultCode in [cls.error_code for cls in fault_classes]: | ||
504 | 153 | return fault | ||
505 | 154 | raise failure | ||
506 | 155 | 137 | ||
507 | === modified file 'lib/lp/codehosting/vfs/tests/test_branchfsclient.py' | |||
508 | --- lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-03-23 02:34:35 +0000 | |||
509 | +++ lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-04-10 13:58:26 +0000 | |||
510 | @@ -9,16 +9,13 @@ | |||
511 | 9 | 9 | ||
512 | 10 | import unittest | 10 | import unittest |
513 | 11 | 11 | ||
514 | 12 | from twisted.python.failure import Failure | ||
515 | 13 | from twisted.trial.unittest import TestCase | 12 | from twisted.trial.unittest import TestCase |
516 | 14 | 13 | ||
517 | 15 | from lp.codehosting.vfs.branchfsclient import ( | 14 | from lp.codehosting.vfs.branchfsclient import ( |
519 | 16 | BranchFileSystemClient, NotInCache, trap_fault) | 15 | BranchFileSystemClient, NotInCache) |
520 | 17 | from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper | 16 | from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper |
521 | 18 | from lp.code.interfaces.codehosting import BRANCH_TRANSPORT | 17 | from lp.code.interfaces.codehosting import BRANCH_TRANSPORT |
522 | 19 | from lp.testing import FakeTime | 18 | from lp.testing import FakeTime |
523 | 20 | from canonical.launchpad.xmlrpc.tests.test_faults import ( | ||
524 | 21 | TestFaultOne, TestFaultTwo) | ||
525 | 22 | 19 | ||
526 | 23 | 20 | ||
527 | 24 | class TestBranchFileSystemClient(TestCase): | 21 | class TestBranchFileSystemClient(TestCase): |
528 | @@ -250,56 +247,6 @@ | |||
529 | 250 | [branch1.unique_name, branch2.unique_name], seen_branches) | 247 | [branch1.unique_name, branch2.unique_name], seen_branches) |
530 | 251 | 248 | ||
531 | 252 | 249 | ||
532 | 253 | class TestTrapFault(TestCase): | ||
533 | 254 | """Tests for `trap_fault`.""" | ||
534 | 255 | |||
535 | 256 | def makeFailure(self, exception_factory, *args, **kwargs): | ||
536 | 257 | """Make a `Failure` from the given exception factory.""" | ||
537 | 258 | try: | ||
538 | 259 | raise exception_factory(*args, **kwargs) | ||
539 | 260 | except: | ||
540 | 261 | return Failure() | ||
541 | 262 | |||
542 | 263 | def assertRaisesFailure(self, failure, function, *args, **kwargs): | ||
543 | 264 | try: | ||
544 | 265 | function(*args, **kwargs) | ||
545 | 266 | except Failure, raised_failure: | ||
546 | 267 | self.assertEqual(failure, raised_failure) | ||
547 | 268 | |||
548 | 269 | def test_raises_non_faults(self): | ||
549 | 270 | # trap_fault re-raises any failures it gets that aren't faults. | ||
550 | 271 | failure = self.makeFailure(RuntimeError, 'example failure') | ||
551 | 272 | self.assertRaisesFailure(failure, trap_fault, failure, TestFaultOne) | ||
552 | 273 | |||
553 | 274 | def test_raises_faults_with_wrong_code(self): | ||
554 | 275 | # trap_fault re-raises any failures it gets that are faults but have | ||
555 | 276 | # the wrong fault code. | ||
556 | 277 | failure = self.makeFailure(TestFaultOne) | ||
557 | 278 | self.assertRaisesFailure(failure, trap_fault, failure, TestFaultTwo) | ||
558 | 279 | |||
559 | 280 | def test_raises_faults_if_no_codes_given(self): | ||
560 | 281 | # If trap_fault is not given any fault codes, it re-raises the fault | ||
561 | 282 | # failure. | ||
562 | 283 | failure = self.makeFailure(TestFaultOne) | ||
563 | 284 | self.assertRaisesFailure(failure, trap_fault, failure) | ||
564 | 285 | |||
565 | 286 | def test_returns_fault_if_code_matches(self): | ||
566 | 287 | # trap_fault returns the Fault inside the Failure if the fault code | ||
567 | 288 | # matches what's given. | ||
568 | 289 | failure = self.makeFailure(TestFaultOne) | ||
569 | 290 | fault = trap_fault(failure, TestFaultOne) | ||
570 | 291 | self.assertEqual(TestFaultOne.error_code, fault.faultCode) | ||
571 | 292 | self.assertEqual(TestFaultOne.msg_template, fault.faultString) | ||
572 | 293 | |||
573 | 294 | def test_returns_fault_if_code_matches_one_of_set(self): | ||
574 | 295 | # trap_fault returns the Fault inside the Failure if the fault code | ||
575 | 296 | # matches even one of the given fault codes. | ||
576 | 297 | failure = self.makeFailure(TestFaultOne) | ||
577 | 298 | fault = trap_fault(failure, TestFaultOne, TestFaultTwo) | ||
578 | 299 | self.assertEqual(TestFaultOne.error_code, fault.faultCode) | ||
579 | 300 | self.assertEqual(TestFaultOne.msg_template, fault.faultString) | ||
580 | 301 | |||
581 | 302 | |||
582 | 303 | def test_suite(): | 250 | def test_suite(): |
583 | 304 | return unittest.TestLoader().loadTestsFromName(__name__) | 251 | return unittest.TestLoader().loadTestsFromName(__name__) |
584 | 305 | 252 | ||
585 | 306 | 253 | ||
586 | === added file 'lib/lp/services/twistedsupport/tests/test_xmlrpc.py' | |||
587 | --- lib/lp/services/twistedsupport/tests/test_xmlrpc.py 1970-01-01 00:00:00 +0000 | |||
588 | +++ lib/lp/services/twistedsupport/tests/test_xmlrpc.py 2010-04-10 13:58:26 +0000 | |||
589 | @@ -0,0 +1,90 @@ | |||
590 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
591 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
592 | 3 | |||
593 | 4 | """Tests for Twisted XML-RPC support.""" | ||
594 | 5 | |||
595 | 6 | __metaclass__ = type | ||
596 | 7 | |||
597 | 8 | import unittest | ||
598 | 9 | |||
599 | 10 | from twisted.python.failure import Failure | ||
600 | 11 | from twisted.trial.unittest import TestCase | ||
601 | 12 | |||
602 | 13 | from lp.services.xmlrpc import LaunchpadFault | ||
603 | 14 | from lp.services.twistedsupport.xmlrpc import trap_fault | ||
604 | 15 | |||
605 | 16 | |||
606 | 17 | class TestFaultOne(LaunchpadFault): | ||
607 | 18 | """An arbitrary subclass of `LaunchpadFault`. | ||
608 | 19 | |||
609 | 20 | This class and `TestFaultTwo` are a pair of distinct `LaunchpadFault` | ||
610 | 21 | subclasses to use in tests. | ||
611 | 22 | """ | ||
612 | 23 | |||
613 | 24 | error_code = 1001 | ||
614 | 25 | msg_template = "Fault one." | ||
615 | 26 | |||
616 | 27 | |||
617 | 28 | class TestFaultTwo(LaunchpadFault): | ||
618 | 29 | """Another arbitrary subclass of `LaunchpadFault`. | ||
619 | 30 | |||
620 | 31 | This class and `TestFaultOne` are a pair of distinct `LaunchpadFault` | ||
621 | 32 | subclasses to use in tests. | ||
622 | 33 | """ | ||
623 | 34 | |||
624 | 35 | error_code = 1002 | ||
625 | 36 | msg_template = "Fault two." | ||
626 | 37 | |||
627 | 38 | |||
628 | 39 | class TestTrapFault(TestCase): | ||
629 | 40 | """Tests for `trap_fault`.""" | ||
630 | 41 | |||
631 | 42 | def makeFailure(self, exception_factory, *args, **kwargs): | ||
632 | 43 | """Make a `Failure` from the given exception factory.""" | ||
633 | 44 | try: | ||
634 | 45 | raise exception_factory(*args, **kwargs) | ||
635 | 46 | except: | ||
636 | 47 | return Failure() | ||
637 | 48 | |||
638 | 49 | def assertRaisesFailure(self, failure, function, *args, **kwargs): | ||
639 | 50 | try: | ||
640 | 51 | function(*args, **kwargs) | ||
641 | 52 | except Failure, raised_failure: | ||
642 | 53 | self.assertEqual(failure, raised_failure) | ||
643 | 54 | |||
644 | 55 | def test_raises_non_faults(self): | ||
645 | 56 | # trap_fault re-raises any failures it gets that aren't faults. | ||
646 | 57 | failure = self.makeFailure(RuntimeError, 'example failure') | ||
647 | 58 | self.assertRaisesFailure(failure, trap_fault, failure, TestFaultOne) | ||
648 | 59 | |||
649 | 60 | def test_raises_faults_with_wrong_code(self): | ||
650 | 61 | # trap_fault re-raises any failures it gets that are faults but have | ||
651 | 62 | # the wrong fault code. | ||
652 | 63 | failure = self.makeFailure(TestFaultOne) | ||
653 | 64 | self.assertRaisesFailure(failure, trap_fault, failure, TestFaultTwo) | ||
654 | 65 | |||
655 | 66 | def test_raises_faults_if_no_codes_given(self): | ||
656 | 67 | # If trap_fault is not given any fault codes, it re-raises the fault | ||
657 | 68 | # failure. | ||
658 | 69 | failure = self.makeFailure(TestFaultOne) | ||
659 | 70 | self.assertRaisesFailure(failure, trap_fault, failure) | ||
660 | 71 | |||
661 | 72 | def test_returns_fault_if_code_matches(self): | ||
662 | 73 | # trap_fault returns the Fault inside the Failure if the fault code | ||
663 | 74 | # matches what's given. | ||
664 | 75 | failure = self.makeFailure(TestFaultOne) | ||
665 | 76 | fault = trap_fault(failure, TestFaultOne) | ||
666 | 77 | self.assertEqual(TestFaultOne.error_code, fault.faultCode) | ||
667 | 78 | self.assertEqual(TestFaultOne.msg_template, fault.faultString) | ||
668 | 79 | |||
669 | 80 | def test_returns_fault_if_code_matches_one_of_set(self): | ||
670 | 81 | # trap_fault returns the Fault inside the Failure if the fault code | ||
671 | 82 | # matches even one of the given fault codes. | ||
672 | 83 | failure = self.makeFailure(TestFaultOne) | ||
673 | 84 | fault = trap_fault(failure, TestFaultOne, TestFaultTwo) | ||
674 | 85 | self.assertEqual(TestFaultOne.error_code, fault.faultCode) | ||
675 | 86 | self.assertEqual(TestFaultOne.msg_template, fault.faultString) | ||
676 | 87 | |||
677 | 88 | |||
678 | 89 | def test_suite(): | ||
679 | 90 | return unittest.TestLoader().loadTestsFromName(__name__) | ||
680 | 0 | 91 | ||
681 | === added file 'lib/lp/services/twistedsupport/xmlrpc.py' | |||
682 | --- lib/lp/services/twistedsupport/xmlrpc.py 1970-01-01 00:00:00 +0000 | |||
683 | +++ lib/lp/services/twistedsupport/xmlrpc.py 2010-04-10 13:58:26 +0000 | |||
684 | @@ -0,0 +1,27 @@ | |||
685 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
686 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
687 | 3 | |||
688 | 4 | """Support for XML-RPC stuff with Twisted.""" | ||
689 | 5 | |||
690 | 6 | __metaclass__ = type | ||
691 | 7 | __all__ = [ | ||
692 | 8 | 'trap_fault', | ||
693 | 9 | ] | ||
694 | 10 | |||
695 | 11 | from twisted.web.xmlrpc import Fault | ||
696 | 12 | |||
697 | 13 | |||
698 | 14 | def trap_fault(failure, *fault_classes): | ||
699 | 15 | """Trap a fault, based on fault code. | ||
700 | 16 | |||
701 | 17 | :param failure: A Twisted L{Failure}. | ||
702 | 18 | :param *fault_codes: `LaunchpadFault` subclasses. | ||
703 | 19 | :raise Failure: if 'failure' is not a Fault failure, or if the fault code | ||
704 | 20 | does not match the given codes. | ||
705 | 21 | :return: The Fault if it matches one of the codes. | ||
706 | 22 | """ | ||
707 | 23 | failure.trap(Fault) | ||
708 | 24 | fault = failure.value | ||
709 | 25 | if fault.faultCode in [cls.error_code for cls in fault_classes]: | ||
710 | 26 | return fault | ||
711 | 27 | raise failure | ||
712 | 0 | 28 | ||
713 | === modified file 'lib/lp/services/utils.py' | |||
714 | --- lib/lp/services/utils.py 2010-03-18 22:53:50 +0000 | |||
715 | +++ lib/lp/services/utils.py 2010-04-10 13:58:26 +0000 | |||
716 | @@ -1,12 +1,16 @@ | |||
717 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the |
718 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
719 | 3 | 3 | ||
720 | 4 | """Generic Python utilities. | ||
721 | 4 | 5 | ||
723 | 5 | """Implementations of the XML-RPC APIs for codehosting.""" | 6 | Functions, lists and so forth. Nothing here that does system calls or network |
724 | 7 | stuff. | ||
725 | 8 | """ | ||
726 | 6 | 9 | ||
727 | 7 | __metaclass__ = type | 10 | __metaclass__ = type |
728 | 8 | __all__ = [ | 11 | __all__ = [ |
729 | 9 | 'iter_split', | 12 | 'iter_split', |
730 | 13 | 'synchronize', | ||
731 | 10 | 'text_delta', | 14 | 'text_delta', |
732 | 11 | 'value_string', | 15 | 'value_string', |
733 | 12 | ] | 16 | ] |
734 | @@ -35,6 +39,21 @@ | |||
735 | 35 | yield splitter.join(tokens[:i]), splitter.join(tokens[i:]) | 39 | yield splitter.join(tokens[:i]), splitter.join(tokens[i:]) |
736 | 36 | 40 | ||
737 | 37 | 41 | ||
738 | 42 | def synchronize(source, target, add, remove): | ||
739 | 43 | """Update 'source' to match 'target' using 'add' and 'remove'. | ||
740 | 44 | |||
741 | 45 | Changes the container 'source' so that it equals 'target', calling 'add' | ||
742 | 46 | with any object in 'target' not in 'source' and 'remove' with any object | ||
743 | 47 | not in 'target' but in 'source'. | ||
744 | 48 | """ | ||
745 | 49 | need_to_add = [obj for obj in target if obj not in source] | ||
746 | 50 | need_to_remove = [obj for obj in source if obj not in target] | ||
747 | 51 | for obj in need_to_add: | ||
748 | 52 | add(obj) | ||
749 | 53 | for obj in need_to_remove: | ||
750 | 54 | remove(obj) | ||
751 | 55 | |||
752 | 56 | |||
753 | 38 | def value_string(item): | 57 | def value_string(item): |
754 | 39 | """Return a unicode string representing value. | 58 | """Return a unicode string representing value. |
755 | 40 | 59 | ||
756 | 41 | 60 | ||
757 | === added file 'lib/lp/services/xmlrpc.py' | |||
758 | --- lib/lp/services/xmlrpc.py 1970-01-01 00:00:00 +0000 | |||
759 | +++ lib/lp/services/xmlrpc.py 2010-04-10 13:58:26 +0000 | |||
760 | @@ -0,0 +1,40 @@ | |||
761 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
762 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
763 | 3 | |||
764 | 4 | """Generic code for XML-RPC in Launchpad.""" | ||
765 | 5 | |||
766 | 6 | __metaclass__ = type | ||
767 | 7 | __all__ = [ | ||
768 | 8 | 'LaunchpadFault', | ||
769 | 9 | ] | ||
770 | 10 | |||
771 | 11 | import xmlrpclib | ||
772 | 12 | |||
773 | 13 | |||
774 | 14 | class LaunchpadFault(xmlrpclib.Fault): | ||
775 | 15 | """Base class for a Launchpad XMLRPC fault. | ||
776 | 16 | |||
777 | 17 | Subclasses should define a unique error_code and a msg_template, | ||
778 | 18 | which will be interpolated with the given keyword arguments. | ||
779 | 19 | """ | ||
780 | 20 | |||
781 | 21 | error_code = None | ||
782 | 22 | msg_template = None | ||
783 | 23 | |||
784 | 24 | def __init__(self, **kw): | ||
785 | 25 | assert self.error_code is not None, ( | ||
786 | 26 | "Subclasses must define error_code.") | ||
787 | 27 | assert self.msg_template is not None, ( | ||
788 | 28 | "Subclasses must define msg_template.") | ||
789 | 29 | msg = self.msg_template % kw | ||
790 | 30 | xmlrpclib.Fault.__init__(self, self.error_code, msg) | ||
791 | 31 | |||
792 | 32 | def __eq__(self, other): | ||
793 | 33 | if not isinstance(other, LaunchpadFault): | ||
794 | 34 | return False | ||
795 | 35 | return ( | ||
796 | 36 | self.faultCode == other.faultCode | ||
797 | 37 | and self.faultString == other.faultString) | ||
798 | 38 | |||
799 | 39 | def __ne__(self, other): | ||
800 | 40 | return not (self == other) |