Merge lp:~jml/launchpad/use-testtools into lp:launchpad
- use-testtools
- Merge into devel
Proposed by
Jonathan Lange
Status: | Merged |
---|---|
Approved by: | Jonathan Lange |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~jml/launchpad/use-testtools |
Merge into: | lp:launchpad |
Diff against target: |
596 lines (+59/-325) 10 files modified
lib/canonical/launchpad/testing/tests/test_testcase.py (+0/-163) lib/lp/codehosting/puller/tests/test_scheduler.py (+5/-0) lib/lp/registry/windmill/tests/test_add_milestone.py.disabled (+1/-0) lib/lp/registry/windmill/tests/test_datetime_picker.py (+1/-0) lib/lp/registry/windmill/tests/test_plusnew_step1.py (+1/-0) lib/lp/registry/windmill/tests/test_plusnew_step2.py (+1/-0) lib/lp/registry/windmill/tests/test_team_index.py (+1/-0) lib/lp/registry/windmill/tests/test_timeline_graph.py (+2/-0) lib/lp/testing/__init__.py (+46/-161) utilities/sourcedeps.conf (+1/-1) |
To merge this branch: | bzr merge lp:~jml/launchpad/use-testtools |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Poster (community) | Approve | ||
Aaron Bentley (community) | Approve | ||
Review via email: mp+16711@code.launchpad.net |
Commit message
Use testtools, really. Make it an egg, and delete all of the Launchpad code that duplicates testtools.
Description of the change
To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote : | # |
Revision history for this message
Jonathan Lange (jml) wrote : | # |
I got over my fear & committed the tarball for testtools to download-cache, like this:
$ bzr st
added:
dist/
Testing now.
Revision history for this message
Aaron Bentley (abentley) wrote : | # |
This looks totally reasonable to me.
review:
Approve
Revision history for this message
Gary Poster (gary) wrote : | # |
You have some diff cruft problems visible in lines 193 through 201 of the diff as I see it right now. Other than that it looks good to me.
Gary
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === removed file 'lib/canonical/launchpad/testing/tests/test_testcase.py' | |||
2 | --- lib/canonical/launchpad/testing/tests/test_testcase.py 2009-06-25 05:30:52 +0000 | |||
3 | +++ lib/canonical/launchpad/testing/tests/test_testcase.py 1970-01-01 00:00:00 +0000 | |||
4 | @@ -1,163 +0,0 @@ | |||
5 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | ||
6 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
7 | 3 | |||
8 | 4 | """Tests for the base TestCase classes.""" | ||
9 | 5 | |||
10 | 6 | __metaclass__ = type | ||
11 | 7 | |||
12 | 8 | import unittest | ||
13 | 9 | from lp.testing import TestCase | ||
14 | 10 | |||
15 | 11 | |||
16 | 12 | class LoggingResult(unittest.TestResult): | ||
17 | 13 | """TestResult that logs its event to a list.""" | ||
18 | 14 | |||
19 | 15 | def __init__(self, log): | ||
20 | 16 | self._events = log | ||
21 | 17 | super(LoggingResult, self).__init__() | ||
22 | 18 | |||
23 | 19 | def startTest(self, test): | ||
24 | 20 | self._events.append(('startTest', test)) | ||
25 | 21 | super(LoggingResult, self).startTest(test) | ||
26 | 22 | |||
27 | 23 | def stopTest(self, test): | ||
28 | 24 | self._events.append(('stopTest', test)) | ||
29 | 25 | super(LoggingResult, self).stopTest(test) | ||
30 | 26 | |||
31 | 27 | def addFailure(self, test, error): | ||
32 | 28 | self._events.append(('addFailure', test, error)) | ||
33 | 29 | super(LoggingResult, self).addFailure(test, error) | ||
34 | 30 | |||
35 | 31 | def addError(self, test, error): | ||
36 | 32 | self._events.append(('addError', test, error)) | ||
37 | 33 | super(LoggingResult, self).addError(test, error) | ||
38 | 34 | |||
39 | 35 | def addSuccess(self, test): | ||
40 | 36 | self._events.append(('addSuccess', test)) | ||
41 | 37 | super(LoggingResult, self).addSuccess(test) | ||
42 | 38 | |||
43 | 39 | |||
44 | 40 | class TestAddCleanup(unittest.TestCase): | ||
45 | 41 | """Tests for TestCase.addCleanup.""" | ||
46 | 42 | |||
47 | 43 | class LoggingTest(TestCase): | ||
48 | 44 | """A test that logs calls to setUp, runTest and tearDown.""" | ||
49 | 45 | |||
50 | 46 | def setUp(self): | ||
51 | 47 | self._calls = ['setUp'] | ||
52 | 48 | |||
53 | 49 | def brokenSetUp(self): | ||
54 | 50 | # A tearDown that deliberately fails. | ||
55 | 51 | self._calls = ['brokenSetUp'] | ||
56 | 52 | raise RuntimeError('Deliberate Failure') | ||
57 | 53 | |||
58 | 54 | def runTest(self): | ||
59 | 55 | self._calls.append('runTest') | ||
60 | 56 | |||
61 | 57 | def tearDown(self): | ||
62 | 58 | self._calls.append('tearDown') | ||
63 | 59 | |||
64 | 60 | def setUp(self): | ||
65 | 61 | self._result_calls = [] | ||
66 | 62 | self.test = TestAddCleanup.LoggingTest('runTest') | ||
67 | 63 | self.logging_result = LoggingResult(self._result_calls) | ||
68 | 64 | |||
69 | 65 | def assertErrorLogEqual(self, messages): | ||
70 | 66 | self.assertEqual(messages, [call[0] for call in self._result_calls]) | ||
71 | 67 | |||
72 | 68 | def assertTestLogEqual(self, messages): | ||
73 | 69 | """Assert that the call log equals `messages`.""" | ||
74 | 70 | self.assertEqual(messages, self.test._calls) | ||
75 | 71 | |||
76 | 72 | def logAppender(self, message): | ||
77 | 73 | """Return a cleanup that appends `message` to the tests log. | ||
78 | 74 | |||
79 | 75 | Cleanups are callables that are added to a test by addCleanup. To | ||
80 | 76 | verify that our cleanups run in the right order, we add strings to a | ||
81 | 77 | list that acts as a log. This method returns a cleanup that will add | ||
82 | 78 | the given message to that log when run. | ||
83 | 79 | """ | ||
84 | 80 | self.test._calls.append(message) | ||
85 | 81 | |||
86 | 82 | def test_fixture(self): | ||
87 | 83 | # A normal run of self.test logs 'setUp', 'runTest' and 'tearDown'. | ||
88 | 84 | # This test doesn't test addCleanup itself, it just sanity checks the | ||
89 | 85 | # fixture. | ||
90 | 86 | self.test.run(self.logging_result) | ||
91 | 87 | self.assertTestLogEqual(['setUp', 'runTest', 'tearDown']) | ||
92 | 88 | |||
93 | 89 | def test_cleanup_run_before_tearDown(self): | ||
94 | 90 | # Cleanup functions added with 'addCleanup' are called before tearDown | ||
95 | 91 | # runs. | ||
96 | 92 | self.test.addCleanup(self.logAppender, 'cleanup') | ||
97 | 93 | self.test.run(self.logging_result) | ||
98 | 94 | self.assertTestLogEqual(['setUp', 'runTest', 'cleanup', 'tearDown']) | ||
99 | 95 | |||
100 | 96 | def test_add_cleanup_called_if_setUp_fails(self): | ||
101 | 97 | # Cleanup functions added with 'addCleanup' are called even if setUp | ||
102 | 98 | # fails. Note that tearDown has a different behavior: it is only | ||
103 | 99 | # called when setUp succeeds. | ||
104 | 100 | self.test.setUp = self.test.brokenSetUp | ||
105 | 101 | self.test.addCleanup(self.logAppender, 'cleanup') | ||
106 | 102 | self.test.run(self.logging_result) | ||
107 | 103 | self.assertTestLogEqual(['brokenSetUp', 'cleanup']) | ||
108 | 104 | |||
109 | 105 | def test_addCleanup_called_in_reverse_order(self): | ||
110 | 106 | # Cleanup functions added with 'addCleanup' are called in reverse | ||
111 | 107 | # order. | ||
112 | 108 | # | ||
113 | 109 | # One of the main uses of addCleanup is to dynamically create | ||
114 | 110 | # resources that need some sort of explicit tearDown. Often one | ||
115 | 111 | # resource will be created in terms of another, e.g., | ||
116 | 112 | # self.first = self.makeFirst() | ||
117 | 113 | # self.second = self.makeSecond(self.first) | ||
118 | 114 | # | ||
119 | 115 | # When this happens, we generally want to clean up the second resource | ||
120 | 116 | # before the first one, since the second depends on the first. | ||
121 | 117 | self.test.addCleanup(self.logAppender, 'first') | ||
122 | 118 | self.test.addCleanup(self.logAppender, 'second') | ||
123 | 119 | self.test.run(self.logging_result) | ||
124 | 120 | self.assertTestLogEqual( | ||
125 | 121 | ['setUp', 'runTest', 'second', 'first', 'tearDown']) | ||
126 | 122 | |||
127 | 123 | def test_tearDown_runs_after_cleanup_failure(self): | ||
128 | 124 | # tearDown runs even if a cleanup function fails. | ||
129 | 125 | self.test.addCleanup(lambda: 1/0) | ||
130 | 126 | self.test.run(self.logging_result) | ||
131 | 127 | self.assertTestLogEqual(['setUp', 'runTest', 'tearDown']) | ||
132 | 128 | |||
133 | 129 | def test_cleanups_continue_running_after_error(self): | ||
134 | 130 | # All cleanups are always run, even if one or two of them fail. | ||
135 | 131 | self.test.addCleanup(self.logAppender, 'first') | ||
136 | 132 | self.test.addCleanup(lambda: 1/0) | ||
137 | 133 | self.test.addCleanup(self.logAppender, 'second') | ||
138 | 134 | self.test.run(self.logging_result) | ||
139 | 135 | self.assertTestLogEqual( | ||
140 | 136 | ['setUp', 'runTest', 'second', 'first', 'tearDown']) | ||
141 | 137 | |||
142 | 138 | def test_error_in_cleanups_are_captured(self): | ||
143 | 139 | # If a cleanup raises an error, we want to record it and fail the | ||
144 | 140 | # test, even though we go on to run other cleanups. | ||
145 | 141 | self.test.addCleanup(lambda: 1/0) | ||
146 | 142 | self.test.run(self.logging_result) | ||
147 | 143 | self.assertErrorLogEqual(['startTest', 'addError', 'stopTest']) | ||
148 | 144 | |||
149 | 145 | def test_keyboard_interrupt_not_caught(self): | ||
150 | 146 | # If a cleanup raises KeyboardInterrupt, it gets reraised. | ||
151 | 147 | def raiseKeyboardInterrupt(): | ||
152 | 148 | raise KeyboardInterrupt() | ||
153 | 149 | self.test.addCleanup(raiseKeyboardInterrupt) | ||
154 | 150 | self.assertRaises( | ||
155 | 151 | KeyboardInterrupt, self.test.run, self.logging_result) | ||
156 | 152 | |||
157 | 153 | def test_multipleErrorsReported(self): | ||
158 | 154 | # Errors from all failing cleanups are reported. | ||
159 | 155 | self.test.addCleanup(lambda: 1/0) | ||
160 | 156 | self.test.addCleanup(lambda: 1/0) | ||
161 | 157 | self.test.run(self.logging_result) | ||
162 | 158 | self.assertErrorLogEqual( | ||
163 | 159 | ['startTest', 'addError', 'addError', 'stopTest']) | ||
164 | 160 | |||
165 | 161 | |||
166 | 162 | def test_suite(): | ||
167 | 163 | return unittest.TestLoader().loadTestsFromName(__name__) | ||
168 | 164 | 0 | ||
169 | === modified file 'lib/lp/codehosting/puller/tests/test_scheduler.py' | |||
170 | --- lib/lp/codehosting/puller/tests/test_scheduler.py 2009-12-22 23:35:26 +0000 | |||
171 | +++ lib/lp/codehosting/puller/tests/test_scheduler.py 2010-01-14 04:34:14 +0000 | |||
172 | @@ -640,6 +640,11 @@ | |||
173 | 640 | 640 | ||
174 | 641 | layer = TwistedAppServerLayer | 641 | layer = TwistedAppServerLayer |
175 | 642 | 642 | ||
176 | 643 | # XXX: Hacky workaround for Trial. It interprets the skip method as a | ||
177 | 644 | # reason to skip the tests, but the default test result object doesn't | ||
178 | 645 | # support skipping, hence errors. | ||
179 | 646 | skip = None | ||
180 | 647 | |||
181 | 643 | def setUp(self): | 648 | def setUp(self): |
182 | 644 | TrialTestCase.setUp(self) | 649 | TrialTestCase.setUp(self) |
183 | 645 | PullerBranchTestCase.setUp(self) | 650 | PullerBranchTestCase.setUp(self) |
184 | 646 | 651 | ||
185 | === modified file 'lib/lp/registry/windmill/tests/test_add_milestone.py.disabled' | |||
186 | --- lib/lp/registry/windmill/tests/test_add_milestone.py.disabled 2010-01-12 22:43:49 +0000 | |||
187 | +++ lib/lp/registry/windmill/tests/test_add_milestone.py.disabled 2010-01-14 04:34:14 +0000 | |||
188 | @@ -87,6 +87,7 @@ | |||
189 | 87 | layer = RegistryWindmillLayer | 87 | layer = RegistryWindmillLayer |
190 | 88 | 88 | ||
191 | 89 | def setUp(self): | 89 | def setUp(self): |
192 | 90 | super(TestAddMilestone, self).setUp() | ||
193 | 90 | self.client = WindmillTestClient('AddMilestone') | 91 | self.client = WindmillTestClient('AddMilestone') |
194 | 91 | 92 | ||
195 | 92 | def test_adding_milestone_on_addrelease_page(self): | 93 | def test_adding_milestone_on_addrelease_page(self): |
196 | 93 | 94 | ||
197 | === modified file 'lib/lp/registry/windmill/tests/test_datetime_picker.py' | |||
198 | --- lib/lp/registry/windmill/tests/test_datetime_picker.py 2009-12-10 17:52:56 +0000 | |||
199 | +++ lib/lp/registry/windmill/tests/test_datetime_picker.py 2010-01-14 04:34:14 +0000 | |||
200 | @@ -22,6 +22,7 @@ | |||
201 | 22 | layer = RegistryWindmillLayer | 22 | layer = RegistryWindmillLayer |
202 | 23 | 23 | ||
203 | 24 | def setUp(self): | 24 | def setUp(self): |
204 | 25 | super(TestDateTimeCalendarWidget, self).setUp() | ||
205 | 25 | self.client = WindmillTestClient('DateTimeCalendarWidget') | 26 | self.client = WindmillTestClient('DateTimeCalendarWidget') |
206 | 26 | 27 | ||
207 | 27 | def test_datetime_calendar_widget(self): | 28 | def test_datetime_calendar_widget(self): |
208 | 28 | 29 | ||
209 | === modified file 'lib/lp/registry/windmill/tests/test_plusnew_step1.py' | |||
210 | --- lib/lp/registry/windmill/tests/test_plusnew_step1.py 2009-12-11 19:54:04 +0000 | |||
211 | +++ lib/lp/registry/windmill/tests/test_plusnew_step1.py 2010-01-14 04:34:14 +0000 | |||
212 | @@ -24,6 +24,7 @@ | |||
213 | 24 | layer = RegistryWindmillLayer | 24 | layer = RegistryWindmillLayer |
214 | 25 | 25 | ||
215 | 26 | def setUp(self): | 26 | def setUp(self): |
216 | 27 | super(TestNewProjectStep1, self).setUp() | ||
217 | 27 | self.client = WindmillTestClient('TestNewProjectStep1') | 28 | self.client = WindmillTestClient('TestNewProjectStep1') |
218 | 28 | 29 | ||
219 | 29 | def test_projects_plusnew_text_fields(self): | 30 | def test_projects_plusnew_text_fields(self): |
220 | 30 | 31 | ||
221 | === modified file 'lib/lp/registry/windmill/tests/test_plusnew_step2.py' | |||
222 | --- lib/lp/registry/windmill/tests/test_plusnew_step2.py 2009-12-11 19:54:04 +0000 | |||
223 | +++ lib/lp/registry/windmill/tests/test_plusnew_step2.py 2010-01-14 04:34:14 +0000 | |||
224 | @@ -22,6 +22,7 @@ | |||
225 | 22 | layer = RegistryWindmillLayer | 22 | layer = RegistryWindmillLayer |
226 | 23 | 23 | ||
227 | 24 | def setUp(self): | 24 | def setUp(self): |
228 | 25 | super(TestNewProjectStep2, self).setUp() | ||
229 | 25 | self.client = WindmillTestClient('TestNewProjectStep2') | 26 | self.client = WindmillTestClient('TestNewProjectStep2') |
230 | 26 | 27 | ||
231 | 27 | def test_projects_plusnew_step_two(self): | 28 | def test_projects_plusnew_step_two(self): |
232 | 28 | 29 | ||
233 | === modified file 'lib/lp/registry/windmill/tests/test_team_index.py' | |||
234 | --- lib/lp/registry/windmill/tests/test_team_index.py 2009-12-18 05:46:01 +0000 | |||
235 | +++ lib/lp/registry/windmill/tests/test_team_index.py 2010-01-14 04:34:14 +0000 | |||
236 | @@ -24,6 +24,7 @@ | |||
237 | 24 | layer = RegistryWindmillLayer | 24 | layer = RegistryWindmillLayer |
238 | 25 | 25 | ||
239 | 26 | def setUp(self): | 26 | def setUp(self): |
240 | 27 | super(TestTeamIndex, self).setUp() | ||
241 | 27 | self.client = WindmillTestClient(__name__) | 28 | self.client = WindmillTestClient(__name__) |
242 | 28 | 29 | ||
243 | 29 | def test_addmember(self): | 30 | def test_addmember(self): |
244 | 30 | 31 | ||
245 | === modified file 'lib/lp/registry/windmill/tests/test_timeline_graph.py' | |||
246 | --- lib/lp/registry/windmill/tests/test_timeline_graph.py 2009-12-10 17:52:56 +0000 | |||
247 | +++ lib/lp/registry/windmill/tests/test_timeline_graph.py 2010-01-14 04:34:14 +0000 | |||
248 | @@ -13,12 +13,14 @@ | |||
249 | 13 | from lp.registry.windmill.testing import RegistryWindmillLayer | 13 | from lp.registry.windmill.testing import RegistryWindmillLayer |
250 | 14 | from lp.testing import TestCaseWithFactory | 14 | from lp.testing import TestCaseWithFactory |
251 | 15 | 15 | ||
252 | 16 | |||
253 | 16 | class TestTimelineGraph(TestCaseWithFactory): | 17 | class TestTimelineGraph(TestCaseWithFactory): |
254 | 17 | """Test timeline graph widget.""" | 18 | """Test timeline graph widget.""" |
255 | 18 | 19 | ||
256 | 19 | layer = RegistryWindmillLayer | 20 | layer = RegistryWindmillLayer |
257 | 20 | 21 | ||
258 | 21 | def setUp(self): | 22 | def setUp(self): |
259 | 23 | super(TestTimelineGraph, self).setUp() | ||
260 | 22 | self.client = WindmillTestClient('TimelineGraph') | 24 | self.client = WindmillTestClient('TimelineGraph') |
261 | 23 | 25 | ||
262 | 24 | def test_timeline_graph(self): | 26 | def test_timeline_graph(self): |
263 | 25 | 27 | ||
264 | === modified file 'lib/lp/testing/__init__.py' | |||
265 | --- lib/lp/testing/__init__.py 2009-12-22 23:50:27 +0000 | |||
266 | +++ lib/lp/testing/__init__.py 2010-01-14 04:34:14 +0000 | |||
267 | @@ -1,24 +1,47 @@ | |||
269 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009, 2010 Canonical Ltd. This software is licensed under the |
270 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
271 | 3 | 3 | ||
272 | 4 | # pylint: disable-msg=W0401,C0301 | 4 | # pylint: disable-msg=W0401,C0301 |
273 | 5 | 5 | ||
274 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
275 | 7 | __all__ = [ | ||
276 | 8 | 'ANONYMOUS', | ||
277 | 9 | 'capture_events', | ||
278 | 10 | 'FakeTime', | ||
279 | 11 | 'get_lsb_information', | ||
280 | 12 | 'is_logged_in', | ||
281 | 13 | 'login', | ||
282 | 14 | 'login_person', | ||
283 | 15 | 'logout', | ||
284 | 16 | 'map_branch_contents', | ||
285 | 17 | 'normalize_whitespace', | ||
286 | 18 | 'record_statements', | ||
287 | 19 | 'run_with_login', | ||
288 | 20 | 'run_with_storm_debug', | ||
289 | 21 | 'run_script', | ||
290 | 22 | 'TestCase', | ||
291 | 23 | 'TestCaseWithFactory', | ||
292 | 24 | 'test_tales', | ||
293 | 25 | 'time_counter', | ||
294 | 26 | # XXX: This really shouldn't be exported from here. People should import | ||
295 | 27 | # it from Zope. | ||
296 | 28 | 'verifyObject', | ||
297 | 29 | 'validate_mock_class', | ||
298 | 30 | 'with_anonymous_login', | ||
299 | 31 | ] | ||
300 | 7 | 32 | ||
301 | 33 | import copy | ||
302 | 8 | from datetime import datetime, timedelta | 34 | from datetime import datetime, timedelta |
303 | 9 | from pprint import pformat | ||
304 | 10 | import copy | ||
305 | 11 | from inspect import getargspec, getmembers, getmro, isclass, ismethod | 35 | from inspect import getargspec, getmembers, getmro, isclass, ismethod |
306 | 12 | import os | 36 | import os |
307 | 37 | from pprint import pformat | ||
308 | 13 | import shutil | 38 | import shutil |
309 | 14 | import subprocess | 39 | import subprocess |
310 | 15 | import tempfile | 40 | import tempfile |
311 | 16 | import time | 41 | import time |
312 | 17 | import unittest | ||
313 | 18 | 42 | ||
314 | 19 | from bzrlib.branch import Branch as BzrBranch | 43 | from bzrlib.branch import Branch as BzrBranch |
315 | 20 | from bzrlib.bzrdir import BzrDir, format_registry | 44 | from bzrlib.bzrdir import BzrDir, format_registry |
316 | 21 | from bzrlib.errors import InvalidURLJoin | ||
317 | 22 | from bzrlib.transport import get_transport | 45 | from bzrlib.transport import get_transport |
318 | 23 | 46 | ||
319 | 24 | import pytz | 47 | import pytz |
320 | @@ -26,7 +49,11 @@ | |||
321 | 26 | from storm.store import Store | 49 | from storm.store import Store |
322 | 27 | from storm.tracer import install_tracer, remove_tracer_type | 50 | from storm.tracer import install_tracer, remove_tracer_type |
323 | 28 | 51 | ||
324 | 52 | import testtools | ||
325 | 29 | import transaction | 53 | import transaction |
326 | 54 | |||
327 | 55 | from twisted.python.util import mergeFunctionMetadata | ||
328 | 56 | |||
329 | 30 | from zope.component import getUtility | 57 | from zope.component import getUtility |
330 | 31 | import zope.event | 58 | import zope.event |
331 | 32 | from zope.interface.verify import verifyClass, verifyObject | 59 | from zope.interface.verify import verifyClass, verifyObject |
332 | @@ -34,18 +61,17 @@ | |||
333 | 34 | isinstance as zope_isinstance, removeSecurityProxy) | 61 | isinstance as zope_isinstance, removeSecurityProxy) |
334 | 35 | 62 | ||
335 | 36 | from canonical.launchpad.webapp import errorlog | 63 | from canonical.launchpad.webapp import errorlog |
336 | 64 | from canonical.config import config | ||
337 | 65 | from canonical.launchpad.webapp.interfaces import ILaunchBag | ||
338 | 37 | from lp.codehosting.vfs import branch_id_to_path, get_multi_server | 66 | from lp.codehosting.vfs import branch_id_to_path, get_multi_server |
339 | 38 | from canonical.config import config | ||
340 | 39 | # Import the login and logout functions here as it is a much better | 67 | # Import the login and logout functions here as it is a much better |
341 | 40 | # place to import them from in tests. | 68 | # place to import them from in tests. |
342 | 41 | from canonical.launchpad.webapp.interfaces import ILaunchBag | ||
343 | 42 | |||
344 | 43 | from lp.testing._login import ( | 69 | from lp.testing._login import ( |
345 | 44 | ANONYMOUS, is_logged_in, login, login_person, logout) | 70 | ANONYMOUS, is_logged_in, login, login_person, logout) |
346 | 71 | # canonical.launchpad.ftests expects test_tales to be imported from here. | ||
347 | 72 | # XXX: JonathanLange 2010-01-01: Why?! | ||
348 | 45 | from lp.testing._tales import test_tales | 73 | from lp.testing._tales import test_tales |
349 | 46 | 74 | ||
350 | 47 | from twisted.python.util import mergeFunctionMetadata | ||
351 | 48 | |||
352 | 49 | # zope.exception demands more of frame objects than twisted.python.failure | 75 | # zope.exception demands more of frame objects than twisted.python.failure |
353 | 50 | # provides in its fake frames. This is enough to make it work with them | 76 | # provides in its fake frames. This is enough to make it work with them |
354 | 51 | # as of 2009-09-16. See https://bugs.edge.launchpad.net/bugs/425113. | 77 | # as of 2009-09-16. See https://bugs.edge.launchpad.net/bugs/425113. |
355 | @@ -164,72 +190,9 @@ | |||
356 | 164 | debug(False) | 190 | debug(False) |
357 | 165 | 191 | ||
358 | 166 | 192 | ||
360 | 167 | class TestCase(unittest.TestCase): | 193 | class TestCase(testtools.TestCase): |
361 | 168 | """Provide Launchpad-specific test facilities.""" | 194 | """Provide Launchpad-specific test facilities.""" |
362 | 169 | 195 | ||
363 | 170 | # Python 2.4 monkeypatch: | ||
364 | 171 | if getattr(unittest.TestCase, '_exc_info', None) is None: | ||
365 | 172 | _exc_info = unittest.TestCase._TestCase__exc_info | ||
366 | 173 | # We would not expect to need to make this property writeable, but | ||
367 | 174 | # twisted.trial.unittest.TestCase.__init__ chooses to write to it in | ||
368 | 175 | # the same way that the __init__ of the standard library's | ||
369 | 176 | # unittest.TestCase.__init__ does, as part of its own method of | ||
370 | 177 | # arranging for pre-2.5 compatibility. | ||
371 | 178 | class MonkeyPatchDescriptor: | ||
372 | 179 | def __get__(self, obj, type): | ||
373 | 180 | return obj._TestCase__testMethodName | ||
374 | 181 | def __set__(self, obj, value): | ||
375 | 182 | obj._TestCase__testMethodName = value | ||
376 | 183 | _testMethodName = MonkeyPatchDescriptor() | ||
377 | 184 | |||
378 | 185 | def __init__(self, *args, **kwargs): | ||
379 | 186 | unittest.TestCase.__init__(self, *args, **kwargs) | ||
380 | 187 | self._cleanups = [] | ||
381 | 188 | |||
382 | 189 | def __str__(self): | ||
383 | 190 | """Return the fully qualified Python name of the test. | ||
384 | 191 | |||
385 | 192 | Zope uses this method to determine how to print the test in the | ||
386 | 193 | runner. We use the test's id in order to make the test easier to find, | ||
387 | 194 | and also so that modifications to the id will show up. This is | ||
388 | 195 | particularly important with bzrlib-style test multiplication. | ||
389 | 196 | """ | ||
390 | 197 | return self.id() | ||
391 | 198 | |||
392 | 199 | def _runCleanups(self, result): | ||
393 | 200 | """Run the cleanups that have been added with addCleanup. | ||
394 | 201 | |||
395 | 202 | See the docstring for addCleanup for more information. | ||
396 | 203 | |||
397 | 204 | Returns True if all cleanups ran without error, False otherwise. | ||
398 | 205 | """ | ||
399 | 206 | ok = True | ||
400 | 207 | while self._cleanups: | ||
401 | 208 | function, arguments, keywordArguments = self._cleanups.pop() | ||
402 | 209 | try: | ||
403 | 210 | function(*arguments, **keywordArguments) | ||
404 | 211 | except KeyboardInterrupt: | ||
405 | 212 | raise | ||
406 | 213 | except: | ||
407 | 214 | result.addError(self, self._exc_info()) | ||
408 | 215 | ok = False | ||
409 | 216 | return ok | ||
410 | 217 | |||
411 | 218 | def addCleanup(self, function, *arguments, **keywordArguments): | ||
412 | 219 | """Add a cleanup function to be called before tearDown. | ||
413 | 220 | |||
414 | 221 | Functions added with addCleanup will be called in reverse order of | ||
415 | 222 | adding after the test method and before tearDown. | ||
416 | 223 | |||
417 | 224 | If a function added with addCleanup raises an exception, the error | ||
418 | 225 | will be recorded as a test error, and the next cleanup will then be | ||
419 | 226 | run. | ||
420 | 227 | |||
421 | 228 | Cleanup functions are always called before a test finishes running, | ||
422 | 229 | even if setUp is aborted by an exception. | ||
423 | 230 | """ | ||
424 | 231 | self._cleanups.append((function, arguments, keywordArguments)) | ||
425 | 232 | |||
426 | 233 | def installFixture(self, fixture): | 196 | def installFixture(self, fixture): |
427 | 234 | """Install 'fixture', an object that has a `setUp` and `tearDown`. | 197 | """Install 'fixture', an object that has a `setUp` and `tearDown`. |
428 | 235 | 198 | ||
429 | @@ -346,27 +309,12 @@ | |||
430 | 346 | self.assertTrue(zope_isinstance(instance, assert_class), | 309 | self.assertTrue(zope_isinstance(instance, assert_class), |
431 | 347 | '%r is not an instance of %r' % (instance, assert_class)) | 310 | '%r is not an instance of %r' % (instance, assert_class)) |
432 | 348 | 311 | ||
433 | 349 | def assertIs(self, expected, observed): | ||
434 | 350 | """Assert that `expected` is the same object as `observed`.""" | ||
435 | 351 | self.assertTrue(expected is observed, | ||
436 | 352 | "%r is not %r" % (expected, observed)) | ||
437 | 353 | |||
438 | 354 | def assertIsNot(self, expected, observed, msg=None): | 312 | def assertIsNot(self, expected, observed, msg=None): |
439 | 355 | """Assert that `expected` is not the same object as `observed`.""" | 313 | """Assert that `expected` is not the same object as `observed`.""" |
440 | 356 | if msg is None: | 314 | if msg is None: |
441 | 357 | msg = "%r is %r" % (expected, observed) | 315 | msg = "%r is %r" % (expected, observed) |
442 | 358 | self.assertTrue(expected is not observed, msg) | 316 | self.assertTrue(expected is not observed, msg) |
443 | 359 | 317 | ||
444 | 360 | def assertIn(self, needle, haystack): | ||
445 | 361 | """Assert that 'needle' is in 'haystack'.""" | ||
446 | 362 | self.assertTrue( | ||
447 | 363 | needle in haystack, '%r not in %r' % (needle, haystack)) | ||
448 | 364 | |||
449 | 365 | def assertNotIn(self, needle, haystack): | ||
450 | 366 | """Assert that 'needle' is not in 'haystack'.""" | ||
451 | 367 | self.assertFalse( | ||
452 | 368 | needle in haystack, '%r in %r' % (needle, haystack)) | ||
453 | 369 | |||
454 | 370 | def assertContentEqual(self, iter1, iter2): | 318 | def assertContentEqual(self, iter1, iter2): |
455 | 371 | """Assert that 'iter1' has the same content as 'iter2'.""" | 319 | """Assert that 'iter1' has the same content as 'iter2'.""" |
456 | 372 | list1 = sorted(iter1) | 320 | list1 = sorted(iter1) |
457 | @@ -374,28 +322,6 @@ | |||
458 | 374 | self.assertEqual( | 322 | self.assertEqual( |
459 | 375 | list1, list2, '%s != %s' % (pformat(list1), pformat(list2))) | 323 | list1, list2, '%s != %s' % (pformat(list1), pformat(list2))) |
460 | 376 | 324 | ||
461 | 377 | def assertRaises(self, excClass, callableObj, *args, **kwargs): | ||
462 | 378 | """Assert that a callable raises a particular exception. | ||
463 | 379 | |||
464 | 380 | :param excClass: As for the except statement, this may be either an | ||
465 | 381 | exception class, or a tuple of classes. | ||
466 | 382 | :param callableObj: A callable, will be passed ``*args`` and | ||
467 | 383 | ``**kwargs``. | ||
468 | 384 | |||
469 | 385 | Returns the exception so that you can examine it. | ||
470 | 386 | """ | ||
471 | 387 | try: | ||
472 | 388 | callableObj(*args, **kwargs) | ||
473 | 389 | except excClass, e: | ||
474 | 390 | return e | ||
475 | 391 | else: | ||
476 | 392 | if getattr(excClass, '__name__', None) is not None: | ||
477 | 393 | excName = excClass.__name__ | ||
478 | 394 | else: | ||
479 | 395 | # probably a tuple | ||
480 | 396 | excName = str(excClass) | ||
481 | 397 | raise self.failureException, "%s not raised" % excName | ||
482 | 398 | |||
483 | 399 | def assertRaisesWithContent(self, exception, exception_content, | 325 | def assertRaisesWithContent(self, exception, exception_content, |
484 | 400 | func, *args): | 326 | func, *args): |
485 | 401 | """Check if the given exception is raised with given content. | 327 | """Check if the given exception is raised with given content. |
486 | @@ -403,15 +329,8 @@ | |||
487 | 403 | If the exception isn't raised or the exception_content doesn't | 329 | If the exception isn't raised or the exception_content doesn't |
488 | 404 | match what was raised an AssertionError is raised. | 330 | match what was raised an AssertionError is raised. |
489 | 405 | """ | 331 | """ |
499 | 406 | exception_name = str(exception).split('.')[-1] | 332 | err = self.assertRaises(exception, func, *args) |
500 | 407 | 333 | self.assertEqual(exception_content, str(err)) | |
492 | 408 | try: | ||
493 | 409 | func(*args) | ||
494 | 410 | except exception, err: | ||
495 | 411 | self.assertEqual(str(err), exception_content) | ||
496 | 412 | else: | ||
497 | 413 | raise AssertionError( | ||
498 | 414 | "'%s' was not raised" % exception_name) | ||
501 | 415 | 334 | ||
502 | 416 | def assertBetween(self, lower_bound, variable, upper_bound): | 335 | def assertBetween(self, lower_bound, variable, upper_bound): |
503 | 417 | """Assert that 'variable' is strictly between two boundaries.""" | 336 | """Assert that 'variable' is strictly between two boundaries.""" |
504 | @@ -425,51 +344,12 @@ | |||
505 | 425 | The config values will be restored during test tearDown. | 344 | The config values will be restored during test tearDown. |
506 | 426 | """ | 345 | """ |
507 | 427 | name = self.factory.getUniqueString() | 346 | name = self.factory.getUniqueString() |
509 | 428 | body = '\n'.join(["%s: %s"%(k, v) for k, v in kwargs.iteritems()]) | 347 | body = '\n'.join(["%s: %s" % (k, v) for k, v in kwargs.iteritems()]) |
510 | 429 | config.push(name, "\n[%s]\n%s\n" % (section, body)) | 348 | config.push(name, "\n[%s]\n%s\n" % (section, body)) |
511 | 430 | self.addCleanup(config.pop, name) | 349 | self.addCleanup(config.pop, name) |
512 | 431 | 350 | ||
513 | 432 | def run(self, result=None): | ||
514 | 433 | if result is None: | ||
515 | 434 | result = self.defaultTestResult() | ||
516 | 435 | result.startTest(self) | ||
517 | 436 | testMethod = getattr(self, self._testMethodName) | ||
518 | 437 | try: | ||
519 | 438 | try: | ||
520 | 439 | self.setUp() | ||
521 | 440 | except KeyboardInterrupt: | ||
522 | 441 | raise | ||
523 | 442 | except: | ||
524 | 443 | result.addError(self, self._exc_info()) | ||
525 | 444 | self._runCleanups(result) | ||
526 | 445 | return | ||
527 | 446 | |||
528 | 447 | ok = False | ||
529 | 448 | try: | ||
530 | 449 | testMethod() | ||
531 | 450 | ok = True | ||
532 | 451 | except self.failureException: | ||
533 | 452 | result.addFailure(self, self._exc_info()) | ||
534 | 453 | except KeyboardInterrupt: | ||
535 | 454 | raise | ||
536 | 455 | except: | ||
537 | 456 | result.addError(self, self._exc_info()) | ||
538 | 457 | |||
539 | 458 | cleanupsOk = self._runCleanups(result) | ||
540 | 459 | try: | ||
541 | 460 | self.tearDown() | ||
542 | 461 | except KeyboardInterrupt: | ||
543 | 462 | raise | ||
544 | 463 | except: | ||
545 | 464 | result.addError(self, self._exc_info()) | ||
546 | 465 | ok = False | ||
547 | 466 | if ok and cleanupsOk: | ||
548 | 467 | result.addSuccess(self) | ||
549 | 468 | finally: | ||
550 | 469 | result.stopTest(self) | ||
551 | 470 | |||
552 | 471 | def setUp(self): | 351 | def setUp(self): |
554 | 472 | unittest.TestCase.setUp(self) | 352 | testtools.TestCase.setUp(self) |
555 | 473 | from lp.testing.factory import ObjectFactory | 353 | from lp.testing.factory import ObjectFactory |
556 | 474 | self.factory = ObjectFactory() | 354 | self.factory = ObjectFactory() |
557 | 475 | 355 | ||
558 | @@ -673,6 +553,8 @@ | |||
559 | 673 | zope.event.subscribers[:] = old_subscribers | 553 | zope.event.subscribers[:] = old_subscribers |
560 | 674 | 554 | ||
561 | 675 | 555 | ||
562 | 556 | # XXX: This doesn't seem like a generically-useful testing function. Perhaps | ||
563 | 557 | # it should go in a sub-module or something? -- jml | ||
564 | 676 | def get_lsb_information(): | 558 | def get_lsb_information(): |
565 | 677 | """Returns a dictionary with the LSB host information. | 559 | """Returns a dictionary with the LSB host information. |
566 | 678 | 560 | ||
567 | @@ -770,6 +652,8 @@ | |||
568 | 770 | return " ".join(string.split()) | 652 | return " ".join(string.split()) |
569 | 771 | 653 | ||
570 | 772 | 654 | ||
571 | 655 | # XXX: This doesn't seem to be a generically useful testing function. Perhaps | ||
572 | 656 | # it should go into a sub-module? -- jml | ||
573 | 773 | def map_branch_contents(branch_url): | 657 | def map_branch_contents(branch_url): |
574 | 774 | """Return all files in branch at `branch_url`. | 658 | """Return all files in branch at `branch_url`. |
575 | 775 | 659 | ||
576 | @@ -794,6 +678,7 @@ | |||
577 | 794 | 678 | ||
578 | 795 | return contents | 679 | return contents |
579 | 796 | 680 | ||
580 | 681 | |||
581 | 797 | def validate_mock_class(mock_class): | 682 | def validate_mock_class(mock_class): |
582 | 798 | """Validate method signatures in mock classes derived from real classes. | 683 | """Validate method signatures in mock classes derived from real classes. |
583 | 799 | 684 | ||
584 | 800 | 685 | ||
585 | === removed symlink 'lib/testtools' | |||
586 | === target was u'../sourcecode/testtools/testtools/' | |||
587 | === modified file 'utilities/sourcedeps.conf' | |||
588 | --- utilities/sourcedeps.conf 2010-01-14 03:48:53 +0000 | |||
589 | +++ utilities/sourcedeps.conf 2010-01-14 04:34:14 +0000 | |||
590 | @@ -13,5 +13,5 @@ | |||
591 | 13 | subunit lp:~launchpad-pqm/subunit/trunk;revno=61 | 13 | subunit lp:~launchpad-pqm/subunit/trunk;revno=61 |
592 | 14 | subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2040 | 14 | subvertpy lp:~launchpad-pqm/subvertpy/trunk;revno=2040 |
593 | 15 | testresources lp:~launchpad-pqm/testresources/dev;revno=16 | 15 | testresources lp:~launchpad-pqm/testresources/dev;revno=16 |
595 | 16 | canonical-identity-provider lp:~launchpad-pqm/canonical-identity-provider/trunk;revno=8900 optional | 16 | canonical-identity-provider lp:~launchpad-pqm/canonical-identity-provider/trunk;revno=8901 optional |
596 | 17 | shipit lp:~launchpad-pqm/shipit/trunk;revno=8901 optional | 17 | shipit lp:~launchpad-pqm/shipit/trunk;revno=8901 optional |
This is a bit preliminary, because there are still XXX comments and I don't really know how to do this sort of thing, but it's worth getting feedback now.
This branch changes Launchpad to use a testtools release, rather than the branch. It then changes our base test case to inherit from testtools.TestCase rather than unittest.TestCase directly. This lets us delete a whole bunch of code.
While looking at lp/testing/ __init_ _.py, I noticed a few things that could be improved. The ones I could easily fix (like the duplicated code in 'assertRaisesWi thContent' ), I fixed. Others, I've marked with XXXs.
I haven't run the tests yet, partly since I'm afraid of committing the tarball for testtools to the download-cache.