Merge ~cjwatson/launchpad:remove-zope.app.testing into launchpad:master
- Git
- lp:~cjwatson/launchpad
- remove-zope.app.testing
- Merge into master
Status: | Merged |
---|---|
Approved by: | Colin Watson |
Approved revision: | 81cc691dc79cd30ffc514788e1fe5fe6804901cb |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~cjwatson/launchpad:remove-zope.app.testing |
Merge into: | launchpad:master |
Prerequisite: | ~cjwatson/launchpad:zope.testbrowser.wsgi |
Diff against target: |
770 lines (+187/-150) 15 files modified
lib/lp/buildmaster/browser/tests/test_builder_views.py (+3/-0) lib/lp/services/profile/profiling.txt (+2/-5) lib/lp/services/webapp/tests/test_authutility.py (+8/-4) lib/lp/services/webapp/tests/test_error.py (+2/-4) lib/lp/services/webapp/tests/test_login.py (+1/-2) lib/lp/services/webapp/tests/test_notifications.py (+6/-3) lib/lp/services/worlddata/tests/test_helpers.py (+1/-1) lib/lp/testing/browser.py (+1/-16) lib/lp/testing/layers.py (+139/-99) lib/lp/testing/pages.py (+19/-11) lib/lp/testopenid/testing/helpers.py (+1/-2) lib/zcml (+1/-0) setup.py (+0/-1) utilities/list-pages (+3/-2) zcml/__init__.py (+0/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ioana Lasc (community) | Approve | ||
Review via email: mp+375206@code.launchpad.net |
Commit message
Remove all remaining uses of zope.app.testing
Description of the change
The main work here is in refactoring functional test setup to use the new style based on zope.component.
The extent to which we have to pretend that ZODB exists during test setup is now even smaller: we just need a couple of adjustments in lp.testing.
The zcml directory now doubles as a (trivial) package, since ZCMLFileLayer expects that.
We have to be a bit more careful in a couple of places:
* PageTestLayer now removes its log handler on tearDown.
* LaunchpadWebSer
* A couple of tests in TestBuilderHist
Ioana Lasc (ilasc) : | # |
Preview Diff
1 | diff --git a/lib/lp/buildmaster/browser/tests/test_builder_views.py b/lib/lp/buildmaster/browser/tests/test_builder_views.py |
2 | index 0da8654..24a735b 100644 |
3 | --- a/lib/lp/buildmaster/browser/tests/test_builder_views.py |
4 | +++ b/lib/lp/buildmaster/browser/tests/test_builder_views.py |
5 | @@ -23,6 +23,7 @@ from lp.buildmaster.interfaces.buildfarmjob import ( |
6 | ) |
7 | from lp.registry.interfaces.person import IPersonSet |
8 | from lp.services.database.sqlbase import flush_database_updates |
9 | +from lp.services.webapp.authorization import clear_cache |
10 | from lp.soyuz.browser.build import getSpecificJobs |
11 | from lp.testing import ( |
12 | celebrity_logged_in, |
13 | @@ -224,6 +225,7 @@ class TestBuilderHistoryView(TestCaseWithFactory, BuildCreationMixin): |
14 | self.createRecipeBuildWithBuilder(builder=self.builder) |
15 | self.createRecipeBuildWithBuilder( |
16 | private_branch=True, builder=self.builder) |
17 | + clear_cache() |
18 | view = create_initialized_view(self.builder, '+history') |
19 | view.setupBuildList() |
20 | |
21 | @@ -233,6 +235,7 @@ class TestBuilderHistoryView(TestCaseWithFactory, BuildCreationMixin): |
22 | self.createRecipeBuildWithBuilder(builder=self.builder) |
23 | self.createRecipeBuildWithBuilder( |
24 | private_branch=True, builder=self.builder) |
25 | + clear_cache() |
26 | view = create_initialized_view(self.builder, '+history') |
27 | private_build_icon_matcher = soupmatchers.HTMLContains( |
28 | soupmatchers.Tag( |
29 | diff --git a/lib/lp/services/profile/profiling.txt b/lib/lp/services/profile/profiling.txt |
30 | index 852a222..9a16054 100644 |
31 | --- a/lib/lp/services/profile/profiling.txt |
32 | +++ b/lib/lp/services/profile/profiling.txt |
33 | @@ -18,10 +18,7 @@ variable. |
34 | |
35 | The pagetests profiler is created by the layer during its setUp. |
36 | |
37 | - >>> from lp.testing.layers import ( |
38 | - ... PageTestLayer, |
39 | - ... wsgi_application, |
40 | - ... ) |
41 | + >>> from lp.testing.layers import PageTestLayer |
42 | |
43 | (Save the existing configuration.) |
44 | |
45 | @@ -58,7 +55,7 @@ other things). |
46 | Requests made with a testbrowser will also be profiled. |
47 | |
48 | >>> from zope.testbrowser.wsgi import Browser |
49 | - >>> browser = Browser(wsgi_app=wsgi_application) |
50 | + >>> browser = Browser() |
51 | >>> browser.open('http://launchpad.test/') |
52 | >>> len(PageTestLayer.profiler.getstats()) > profile_count |
53 | True |
54 | diff --git a/lib/lp/services/webapp/tests/test_authutility.py b/lib/lp/services/webapp/tests/test_authutility.py |
55 | index d2dfcbc..279dd3d 100644 |
56 | --- a/lib/lp/services/webapp/tests/test_authutility.py |
57 | +++ b/lib/lp/services/webapp/tests/test_authutility.py |
58 | @@ -6,14 +6,16 @@ __metaclass__ = type |
59 | import base64 |
60 | |
61 | import testtools |
62 | -from zope.app.testing.placelesssetup import PlacelessSetup |
63 | from zope.authentication.interfaces import ILoginPassword |
64 | from zope.component import getUtility |
65 | +from zope.container.testing import ContainerPlacelessSetup |
66 | from zope.interface import implementer |
67 | from zope.principalregistry.principalregistry import UnauthenticatedPrincipal |
68 | from zope.publisher.browser import TestRequest |
69 | from zope.publisher.http import BasicAuthAdapter |
70 | from zope.publisher.interfaces.http import IHTTPCredentials |
71 | +from zope.security.management import newInteraction |
72 | +from zope.security.testing import addCheckerPublic |
73 | |
74 | from lp.registry.interfaces.person import IPerson |
75 | from lp.services.config import config |
76 | @@ -58,11 +60,13 @@ class DummyPlacelessLoginSource(object): |
77 | return [Bruce] |
78 | |
79 | |
80 | -class TestPlacelessAuth(PlacelessSetup, testtools.TestCase): |
81 | +class TestPlacelessAuth(ContainerPlacelessSetup, testtools.TestCase): |
82 | |
83 | def setUp(self): |
84 | testtools.TestCase.setUp(self) |
85 | - PlacelessSetup.setUp(self) |
86 | + ContainerPlacelessSetup.setUp(self) |
87 | + addCheckerPublic() |
88 | + newInteraction() |
89 | self.useFixture(ZopeUtilityFixture( |
90 | DummyPlacelessLoginSource(), IPlacelessLoginSource)) |
91 | self.useFixture(ZopeUtilityFixture( |
92 | @@ -71,7 +75,7 @@ class TestPlacelessAuth(PlacelessSetup, testtools.TestCase): |
93 | BasicAuthAdapter, (IHTTPCredentials,), ILoginPassword)) |
94 | |
95 | def tearDown(self): |
96 | - PlacelessSetup.tearDown(self) |
97 | + ContainerPlacelessSetup.tearDown(self) |
98 | testtools.TestCase.tearDown(self) |
99 | |
100 | def _make(self, login, pwd): |
101 | diff --git a/lib/lp/services/webapp/tests/test_error.py b/lib/lp/services/webapp/tests/test_error.py |
102 | index 3170ae3..7f52168 100644 |
103 | --- a/lib/lp/services/webapp/tests/test_error.py |
104 | +++ b/lib/lp/services/webapp/tests/test_error.py |
105 | @@ -8,7 +8,6 @@ import httplib |
106 | import logging |
107 | import socket |
108 | import time |
109 | -import urllib2 |
110 | |
111 | from fixtures import FakeLogger |
112 | import psycopg2 |
113 | @@ -44,7 +43,6 @@ from lp.testing.fixture import ( |
114 | from lp.testing.layers import ( |
115 | DatabaseLayer, |
116 | LaunchpadFunctionalLayer, |
117 | - wsgi_application, |
118 | ) |
119 | from lp.testing.matchers import Contains |
120 | |
121 | @@ -80,7 +78,7 @@ class TestDatabaseErrorViews(TestCase): |
122 | |
123 | def getHTTPError(self, url): |
124 | try: |
125 | - Browser(wsgi_app=wsgi_application).open(url) |
126 | + Browser().open(url) |
127 | except HTTPError as error: |
128 | return error |
129 | else: |
130 | @@ -107,7 +105,7 @@ class TestDatabaseErrorViews(TestCase): |
131 | |
132 | Raise a TimeoutException if the connection cannot be established. |
133 | """ |
134 | - browser = Browser(wsgi_app=wsgi_application) |
135 | + browser = Browser() |
136 | for i in range(retries): |
137 | try: |
138 | browser.open(url) |
139 | diff --git a/lib/lp/services/webapp/tests/test_login.py b/lib/lp/services/webapp/tests/test_login.py |
140 | index 3d93b8c..9aaae8f 100644 |
141 | --- a/lib/lp/services/webapp/tests/test_login.py |
142 | +++ b/lib/lp/services/webapp/tests/test_login.py |
143 | @@ -81,7 +81,6 @@ from lp.testing.layers import ( |
144 | AppServerLayer, |
145 | DatabaseFunctionalLayer, |
146 | FunctionalLayer, |
147 | - wsgi_application, |
148 | ) |
149 | from lp.testing.pages import ( |
150 | extract_text, |
151 | @@ -732,7 +731,7 @@ class TestMissingServerShowsNiceErrorPage(TestCase): |
152 | |
153 | fixture.replacement = OpenIDLoginThatFailsDiscovery |
154 | self.useFixture(fixture) |
155 | - browser = TestBrowser(wsgi_app=wsgi_application) |
156 | + browser = TestBrowser() |
157 | self.assertRaises(HTTPError, |
158 | browser.open, 'http://launchpad.test/+login') |
159 | self.assertEqual('503 Service Unavailable', |
160 | diff --git a/lib/lp/services/webapp/tests/test_notifications.py b/lib/lp/services/webapp/tests/test_notifications.py |
161 | index c516ac6..7473838 100644 |
162 | --- a/lib/lp/services/webapp/tests/test_notifications.py |
163 | +++ b/lib/lp/services/webapp/tests/test_notifications.py |
164 | @@ -8,8 +8,11 @@ __metaclass__ = type |
165 | from doctest import DocTestSuite |
166 | import unittest |
167 | |
168 | -from zope.app.testing import placelesssetup |
169 | from zope.component import provideAdapter |
170 | +from zope.container.testing import ( |
171 | + setUp as containerSetUp, |
172 | + tearDown as containerTearDown, |
173 | + ) |
174 | from zope.interface import implementer |
175 | from zope.publisher.browser import TestRequest |
176 | from zope.publisher.interfaces.browser import IBrowserRequest |
177 | @@ -68,7 +71,7 @@ def adaptNotificationRequestToResponse(request): |
178 | |
179 | |
180 | def setUp(test): |
181 | - placelesssetup.setUp() |
182 | + containerSetUp() |
183 | mock_session = MockSession() |
184 | provideAdapter(lambda x: mock_session, (INotificationRequest,), ISession) |
185 | provideAdapter(lambda x: mock_session, (INotificationResponse,), ISession) |
186 | @@ -86,7 +89,7 @@ def setUp(test): |
187 | |
188 | |
189 | def tearDown(test): |
190 | - placelesssetup.tearDown() |
191 | + containerTearDown() |
192 | |
193 | |
194 | def test_suite(): |
195 | diff --git a/lib/lp/services/worlddata/tests/test_helpers.py b/lib/lp/services/worlddata/tests/test_helpers.py |
196 | index 54a8a2f..a129604 100644 |
197 | --- a/lib/lp/services/worlddata/tests/test_helpers.py |
198 | +++ b/lib/lp/services/worlddata/tests/test_helpers.py |
199 | @@ -95,8 +95,8 @@ class DummyLaunchBag: |
200 | |
201 | def test_preferred_or_request_languages(): |
202 | ''' |
203 | - >>> from zope.app.testing.placelesssetup import setUp, tearDown |
204 | >>> from zope.component import provideAdapter, provideUtility |
205 | + >>> from zope.container.testing import setUp, tearDown |
206 | >>> from zope.i18n.interfaces import IUserPreferredLanguages |
207 | >>> from lp.services.geoip.interfaces import IRequestPreferredLanguages |
208 | >>> from lp.services.geoip.interfaces import IRequestLocalLanguages |
209 | diff --git a/lib/lp/testing/browser.py b/lib/lp/testing/browser.py |
210 | index 7f4284b..7bacfbe 100644 |
211 | --- a/lib/lp/testing/browser.py |
212 | +++ b/lib/lp/testing/browser.py |
213 | @@ -17,7 +17,6 @@ __all__ = [ |
214 | import ssl |
215 | |
216 | from lazr.uri import URI |
217 | -import transaction |
218 | from urllib3 import PoolManager |
219 | from wsgiproxy.proxies import TransparentProxy |
220 | from wsgiproxy.urllib3_client import HttpClient |
221 | @@ -26,6 +25,7 @@ from zope.testbrowser.wsgi import ( |
222 | Browser as _Browser, |
223 | ) |
224 | |
225 | +from lp.testing.layers import TransactionMiddleware |
226 | from lp.testing.pages import ( |
227 | extract_text, |
228 | find_main_content, |
229 | @@ -34,21 +34,6 @@ from lp.testing.pages import ( |
230 | ) |
231 | |
232 | |
233 | -class TransactionMiddleware: |
234 | - """Middleware to commit the current transaction before the test. |
235 | - |
236 | - This is like `zope.app.wsgi.TransactionMiddleware`, but avoids ZODB. |
237 | - """ |
238 | - |
239 | - def __init__(self, app): |
240 | - self.app = app |
241 | - |
242 | - def __call__(self, environ, start_response): |
243 | - transaction.commit() |
244 | - for entry in self.app(environ, start_response): |
245 | - yield entry |
246 | - |
247 | - |
248 | class Browser(_Browser): |
249 | |
250 | def __init__(self, url=None, wsgi_app=None): |
251 | diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py |
252 | index c38af8e..b90c853 100644 |
253 | --- a/lib/lp/testing/layers.py |
254 | +++ b/lib/lp/testing/layers.py |
255 | @@ -50,7 +50,6 @@ __all__ = [ |
256 | 'ZopelessLayer', |
257 | 'disconnect_stores', |
258 | 'reconnect_stores', |
259 | - 'wsgi_application', |
260 | ] |
261 | |
262 | from cProfile import Profile |
263 | @@ -86,26 +85,24 @@ import transaction |
264 | from webob.request import environ_from_url as orig_environ_from_url |
265 | import wsgi_intercept |
266 | from wsgi_intercept import httplib2_intercept |
267 | -from zope.app.publication.httpfactory import ( |
268 | - chooseClasses, |
269 | - HTTPPublicationRequestFactory, |
270 | - ) |
271 | -from zope.app.testing.functional import ( |
272 | - FunctionalTestSetup, |
273 | - ZopePublication, |
274 | - ) |
275 | +from zope.app.publication.httpfactory import HTTPPublicationRequestFactory |
276 | +from zope.app.wsgi import WSGIPublisherApplication |
277 | from zope.component import ( |
278 | getUtility, |
279 | globalregistry, |
280 | provideUtility, |
281 | ) |
282 | from zope.component.interfaces import ComponentLookupError |
283 | -import zope.publisher.publish |
284 | +from zope.component.testlayer import ZCMLFileLayer |
285 | +from zope.event import notify |
286 | +from zope.processlifetime import DatabaseOpened |
287 | from zope.security.management import ( |
288 | endInteraction, |
289 | getSecurityPolicy, |
290 | ) |
291 | from zope.server.logger.pythonlogger import PythonLogger |
292 | +import zope.testbrowser.wsgi |
293 | +from zope.testbrowser.wsgi import AuthorizationMiddleware |
294 | |
295 | from lp.services import pidfile |
296 | from lp.services.auditor.server import AuditorServer |
297 | @@ -157,16 +154,13 @@ from lp.testing import ( |
298 | ) |
299 | from lp.testing.pgsql import PgTestSetup |
300 | from lp.testing.smtpd import SMTPController |
301 | +import zcml |
302 | |
303 | |
304 | COMMA = ',' |
305 | WAIT_INTERVAL = datetime.timedelta(seconds=180) |
306 | |
307 | |
308 | -def set_up_functional_test(): |
309 | - return FunctionalTestSetup('zcml/ftesting.zcml') |
310 | - |
311 | - |
312 | class LayerError(Exception): |
313 | pass |
314 | |
315 | @@ -258,20 +252,6 @@ def wait_children(seconds=120): |
316 | break |
317 | |
318 | |
319 | -class MockRootFolder: |
320 | - """Implement the minimum functionality required by Z3 ZODB dependencies |
321 | - |
322 | - Installed as part of FunctionalLayer.testSetUp() to allow the http() |
323 | - method (zope.app.testing.functional.HTTPCaller) to work. |
324 | - """ |
325 | - @property |
326 | - def _p_jar(self): |
327 | - return self |
328 | - |
329 | - def sync(self): |
330 | - pass |
331 | - |
332 | - |
333 | class BaseLayer: |
334 | """Base layer. |
335 | |
336 | @@ -993,59 +973,110 @@ class LaunchpadLayer(LibrarianLayer, MemcachedLayer, RabbitMQLayer): |
337 | "DELETE FROM SessionData") |
338 | |
339 | |
340 | -def raw_wsgi_application(environ, start_response): |
341 | - """This is a wsgi application for Zope functional testing. |
342 | +class TransactionMiddleware: |
343 | + """Middleware to commit the current transaction before the test. |
344 | + |
345 | + This is like `zope.app.wsgi.testlayer.TransactionMiddleware`, but avoids |
346 | + ZODB. |
347 | + """ |
348 | + |
349 | + def __init__(self, app): |
350 | + self.app = app |
351 | + |
352 | + def __call__(self, environ, start_response): |
353 | + transaction.commit() |
354 | + for entry in self.app(environ, start_response): |
355 | + yield entry |
356 | + |
357 | + |
358 | +class RemoteAddrMiddleware: |
359 | + """Middleware to set a default for `REMOTE_ADDR`. |
360 | + |
361 | + zope.app.testing.functional.HTTPCaller used to set this, but WebTest |
362 | + doesn't. However, some tests rely on it. |
363 | + """ |
364 | + |
365 | + def __init__(self, app): |
366 | + self.app = app |
367 | + |
368 | + def __call__(self, environ, start_response): |
369 | + environ.setdefault('REMOTE_ADDR', wsgi_native_string('127.0.0.1')) |
370 | + return self.app(environ, start_response) |
371 | + |
372 | + |
373 | +class SortHeadersMiddleware: |
374 | + """Middleware to sort response headers. |
375 | + |
376 | + This makes it easier to write reliable tests. |
377 | + """ |
378 | + |
379 | + def __init__(self, app): |
380 | + self.app = app |
381 | + |
382 | + def __call__(self, environ, start_response): |
383 | + def wrap_start_response(status, response_headers, exc_info=None): |
384 | + return start_response(status, sorted(response_headers), exc_info) |
385 | + |
386 | + return self.app(environ, wrap_start_response) |
387 | + |
388 | |
389 | - We use it with wsgi_intercept, which is itself mostly interesting |
390 | - for our webservice (lazr.restful) tests. |
391 | +class _FunctionalBrowserLayer(zope.testbrowser.wsgi.Layer, ZCMLFileLayer): |
392 | + """A variant of zope.app.wsgi.testlayer.BrowserLayer for FunctionalLayer. |
393 | + |
394 | + This is not a layer for use in Launchpad tests (hence the leading |
395 | + underscore), as zope.component's layer composition strategy is different |
396 | + from the one zope.testrunner expects. |
397 | """ |
398 | - # Committing work done up to now is a convenience that the Zope |
399 | - # zope.app.testing.functional.HTTPCaller does. We're replacing that bit, |
400 | - # so it is easiest to follow that lead, even if it feels a little loose. |
401 | - transaction.commit() |
402 | - # Let's support post-mortem debugging. |
403 | - if environ.pop('HTTP_X_ZOPE_HANDLE_ERRORS', 'True') == 'False': |
404 | - environ['wsgi.handleErrors'] = False |
405 | - handle_errors = environ.get('wsgi.handleErrors', True) |
406 | - |
407 | - # Make sure the request method is something Launchpad will |
408 | - # recognize. httplib2 usually takes care of this, but we've |
409 | - # bypassed that code in our test environment. |
410 | - environ['REQUEST_METHOD'] = environ['REQUEST_METHOD'].upper() |
411 | - # zope.app.testing.functional.HTTPCaller used to set this, but WebTest |
412 | - # doesn't. However, some tests rely on it. |
413 | - environ.setdefault('REMOTE_ADDR', wsgi_native_string('127.0.0.1')) |
414 | - # Now we do the proper dance to get the desired request. This is an |
415 | - # almalgam of code from zope.app.testing.functional.HTTPCaller and |
416 | - # zope.publisher.paste.Application. |
417 | - request_cls, publication_cls = chooseClasses( |
418 | - environ['REQUEST_METHOD'], environ) |
419 | - publication = publication_cls(set_up_functional_test().db) |
420 | - request = request_cls(environ['wsgi.input'], environ) |
421 | - request.setPublication(publication) |
422 | - # The rest of this function is an amalgam of |
423 | - # zope.publisher.paste.Application.__call__ and van.testing.layers. |
424 | - request = zope.publisher.publish.publish( |
425 | - request, handle_errors=handle_errors) |
426 | - response = request.response |
427 | - # We sort these because it makes it easier to write reliable tests. |
428 | - headers = sorted(response.getHeaders()) |
429 | - status = response.getStatusString() |
430 | - # Start the WSGI server response. |
431 | - start_response(status, headers) |
432 | - # Return the result body iterable. |
433 | - return response.consumeBodyIter() |
434 | - |
435 | - |
436 | -_wsgi_application_middlewares = [] |
437 | - |
438 | - |
439 | -def wsgi_application(environ, start_response): |
440 | - """As `raw_wsgi_application`, but possibly wrapped in middleware.""" |
441 | - app = raw_wsgi_application |
442 | - for middleware in reversed(_wsgi_application_middlewares): |
443 | - app = middleware(app) |
444 | - return app(environ, start_response) |
445 | + |
446 | + # A meaningless object passed to publication classes that just require |
447 | + # something other than None. In Zope this would be a ZODB connection, |
448 | + # but we don't use ZODB in Launchpad. |
449 | + fake_db = object() |
450 | + |
451 | + def __init__(self, *args, **kwargs): |
452 | + super(_FunctionalBrowserLayer, self).__init__(*args, **kwargs) |
453 | + self.middlewares = [ |
454 | + AuthorizationMiddleware, |
455 | + RemoteAddrMiddleware, |
456 | + SortHeadersMiddleware, |
457 | + TransactionMiddleware, |
458 | + ] |
459 | + |
460 | + def setUp(self): |
461 | + super(_FunctionalBrowserLayer, self).setUp() |
462 | + # We don't use ZODB, but the webapp subscribes to IDatabaseOpened to |
463 | + # perform some post-configuration tasks, so emit that event |
464 | + # manually. |
465 | + notify(DatabaseOpened(None)) |
466 | + |
467 | + def _resetWSGIApp(self): |
468 | + """Reset `zope.testbrowser.wsgi.Layer.get_app`'s cache. |
469 | + |
470 | + `zope.testbrowser.wsgi.Layer` sets up a cached WSGI application in |
471 | + `setUp` and assumes that it won't change for the lifetime of the |
472 | + layer. This assumption doesn't hold in Launchpad, but |
473 | + `zope.testbrowser.wsgi.Layer` doesn't provide a straightforward way |
474 | + to avoid making it. We do our best. |
475 | + """ |
476 | + zope.testbrowser.wsgi._APP_UNDER_TEST = self.make_wsgi_app() |
477 | + |
478 | + def addMiddlewares(self, *middlewares): |
479 | + self.middlewares.extend(middlewares) |
480 | + self._resetWSGIApp() |
481 | + |
482 | + def removeMiddlewares(self, *middlewares): |
483 | + for middleware in middlewares: |
484 | + self.middlewares.remove(middleware) |
485 | + self._resetWSGIApp() |
486 | + |
487 | + def setupMiddleware(self, app): |
488 | + for middleware in self.middlewares: |
489 | + app = middleware(app) |
490 | + return app |
491 | + |
492 | + def make_wsgi_app(self): |
493 | + """See `zope.testbrowser.wsgi.Layer`.""" |
494 | + return self.setupMiddleware(WSGIPublisherApplication(self.fake_db)) |
495 | |
496 | |
497 | class FunctionalLayer(BaseLayer): |
498 | @@ -1058,9 +1089,17 @@ class FunctionalLayer(BaseLayer): |
499 | @profiled |
500 | def setUp(cls): |
501 | FunctionalLayer.isSetUp = True |
502 | - set_up_functional_test().setUp() |
503 | |
504 | - # Assert that set_up_functional_test did what it says it does |
505 | + # zope.component.testlayer.LayerBase has a different strategy for |
506 | + # layer composition that doesn't play well with zope.testrunner's |
507 | + # approach to setting up and tearing down individual layers. Work |
508 | + # around this by creating a BrowserLayer instance here rather than |
509 | + # having this layer subclass it. |
510 | + FunctionalLayer.browser_layer = _FunctionalBrowserLayer( |
511 | + zcml, zcml_file='ftesting.zcml') |
512 | + FunctionalLayer.browser_layer.setUp() |
513 | + |
514 | + # Assert that ZCMLFileLayer did what it says it does |
515 | if not is_ca_available(): |
516 | raise LayerInvariantError("Component architecture failed to load") |
517 | |
518 | @@ -1068,14 +1107,18 @@ class FunctionalLayer(BaseLayer): |
519 | # If we don't, it may issue extra queries depending on test order. |
520 | lp.services.webapp.session.idmanager.secret |
521 | # If our request publication factories were defined using ZCML, |
522 | - # they'd be set up by set_up_functional_test().setUp(). Since |
523 | - # they're defined by Python code, we need to call that code |
524 | - # here. |
525 | + # they'd be set up by ZCMLFileLayer. Since they're defined by Python |
526 | + # code, we need to call that code here. |
527 | register_launchpad_request_publication_factories() |
528 | + |
529 | + # Most tests use the WSGI application directly via |
530 | + # zope.testbrowser.wsgi.Layer.get_app, but some (especially those |
531 | + # that use lazr.restfulclient or launchpadlib) still talk to the app |
532 | + # server over HTTP and need to be intercepted. |
533 | wsgi_intercept.add_wsgi_intercept( |
534 | - 'localhost', 80, lambda: wsgi_application) |
535 | + 'localhost', 80, _FunctionalBrowserLayer.get_app) |
536 | wsgi_intercept.add_wsgi_intercept( |
537 | - 'api.launchpad.test', 80, lambda: wsgi_application) |
538 | + 'api.launchpad.test', 80, _FunctionalBrowserLayer.get_app) |
539 | httplib2_intercept.install() |
540 | |
541 | # webob.request.environ_from_url defaults to HTTP/1.0, which is |
542 | @@ -1099,6 +1142,7 @@ class FunctionalLayer(BaseLayer): |
543 | wsgi_intercept.remove_wsgi_intercept('localhost', 80) |
544 | wsgi_intercept.remove_wsgi_intercept('api.launchpad.test', 80) |
545 | httplib2_intercept.uninstall() |
546 | + FunctionalLayer.browser_layer.tearDown() |
547 | # Signal Layer cannot be torn down fully |
548 | raise NotImplementedError |
549 | |
550 | @@ -1108,13 +1152,6 @@ class FunctionalLayer(BaseLayer): |
551 | transaction.abort() |
552 | transaction.begin() |
553 | |
554 | - # Fake a root folder to keep Z3 ZODB dependencies happy. |
555 | - fs = set_up_functional_test() |
556 | - if not fs.connection: |
557 | - fs.connection = fs.db.open() |
558 | - root = fs.connection.root() |
559 | - root[ZopePublication.root_name] = MockRootFolder() |
560 | - |
561 | # Allow the WSGI test browser to talk to our various test hosts. |
562 | def assert_allowed_host(self): |
563 | host = self.host |
564 | @@ -1128,6 +1165,7 @@ class FunctionalLayer(BaseLayer): |
565 | 'zope.testbrowser.wsgi.WSGIConnection.assert_allowed_host', |
566 | assert_allowed_host) |
567 | FunctionalLayer._testbrowser_allowed.setUp() |
568 | + FunctionalLayer.browser_layer.testSetUp() |
569 | |
570 | # Should be impossible, as the CA cannot be unloaded. Something |
571 | # mighty nasty has happened if this is triggered. |
572 | @@ -1138,6 +1176,7 @@ class FunctionalLayer(BaseLayer): |
573 | @classmethod |
574 | @profiled |
575 | def testTearDown(cls): |
576 | + FunctionalLayer.browser_layer.testTearDown() |
577 | FunctionalLayer._testbrowser_allowed.cleanUp() |
578 | |
579 | # Should be impossible, as the CA cannot be unloaded. Something |
580 | @@ -1676,18 +1715,19 @@ class PageTestLayer(LaunchpadFunctionalLayer, BingServiceLayer): |
581 | ProfilingMiddleware, profiler=PageTestLayer.profiler) |
582 | PageTestLayer._access_logging_middleware = partial( |
583 | AccessLoggingMiddleware, access_logger=access_logger) |
584 | - _wsgi_application_middlewares.extend([ |
585 | + FunctionalLayer.browser_layer.addMiddlewares( |
586 | PageTestLayer._profiling_middleware, |
587 | - PageTestLayer._access_logging_middleware, |
588 | - ]) |
589 | + PageTestLayer._access_logging_middleware) |
590 | |
591 | @classmethod |
592 | @profiled |
593 | def tearDown(cls): |
594 | - _wsgi_application_middlewares.remove( |
595 | - PageTestLayer._access_logging_middleware) |
596 | - _wsgi_application_middlewares.remove( |
597 | + FunctionalLayer.browser_layer.removeMiddlewares( |
598 | + PageTestLayer._access_logging_middleware, |
599 | PageTestLayer._profiling_middleware) |
600 | + logger = PythonLogger('pagetests-access') |
601 | + for handler in list(logger.logger.handlers): |
602 | + logger.logger.removeHandler(handler) |
603 | if PageTestLayer.profiler: |
604 | PageTestLayer.profiler.dump_stats( |
605 | os.environ.get('PROFILE_PAGETESTS_REQUESTS')) |
606 | diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py |
607 | index 311feac..cff8368 100644 |
608 | --- a/lib/lp/testing/pages.py |
609 | +++ b/lib/lp/testing/pages.py |
610 | @@ -46,14 +46,17 @@ from lazr.restful.testing.webservice import WebServiceCaller |
611 | import six |
612 | import transaction |
613 | from webtest import TestRequest |
614 | -from zope.app.wsgi.testlayer import FakeResponse as _FakeResponse |
615 | +from zope.app.wsgi.testlayer import ( |
616 | + FakeResponse as _FakeResponse, |
617 | + NotInBrowserLayer, |
618 | + ) |
619 | from zope.component import getUtility |
620 | from zope.security.management import setSecurityPolicy |
621 | from zope.security.proxy import removeSecurityProxy |
622 | from zope.session.interfaces import ISession |
623 | from zope.testbrowser.wsgi import ( |
624 | - AuthorizationMiddleware, |
625 | Browser, |
626 | + Layer as TestBrowserWSGILayer, |
627 | ) |
628 | |
629 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
630 | @@ -83,10 +86,7 @@ from lp.testing import ( |
631 | ) |
632 | from lp.testing.dbuser import dbuser |
633 | from lp.testing.factory import LaunchpadObjectFactory |
634 | -from lp.testing.layers import ( |
635 | - PageTestLayer, |
636 | - wsgi_application, |
637 | - ) |
638 | +from lp.testing.layers import PageTestLayer |
639 | from lp.testing.systemdocs import ( |
640 | LayeredDocFileSuite, |
641 | stop, |
642 | @@ -103,8 +103,8 @@ SAMPLEDATA_ACCESS_SECRETS = { |
643 | class FakeResponse(_FakeResponse): |
644 | """A fake response for use in tests. |
645 | |
646 | - This is like `zope.app.wsgi.FakeResponse`, but does a better job of |
647 | - emulating `zope.app.testing.functional` by using the request's |
648 | + This is like `zope.app.wsgi.testlayer.FakeResponse`, but does a better |
649 | + job of emulating `zope.app.testing.functional` by using the request's |
650 | `SERVER_PROTOCOL` in the response. |
651 | """ |
652 | |
653 | @@ -133,6 +133,10 @@ def http(string, handle_errors=True): |
654 | `SERVER_PORT` to 80, which confuses |
655 | `VirtualHostRequestPublicationFactory.canHandle`. |
656 | """ |
657 | + app = TestBrowserWSGILayer.get_app() |
658 | + if app is None: |
659 | + raise NotInBrowserLayer(NotInBrowserLayer.__doc__) |
660 | + |
661 | if not isinstance(string, bytes): |
662 | string = string.encode('UTF-8') |
663 | request = TestRequest.from_file(BytesIO(string.lstrip())) |
664 | @@ -145,7 +149,7 @@ def http(string, handle_errors=True): |
665 | port = 80 |
666 | request.environ['SERVER_NAME'] = host |
667 | request.environ['SERVER_PORT'] = int(port) |
668 | - response = request.get_response(AuthorizationMiddleware(wsgi_application)) |
669 | + response = request.get_response(app) |
670 | return FakeResponse(response, request) |
671 | |
672 | |
673 | @@ -191,7 +195,11 @@ class LaunchpadWebServiceCaller(WebServiceCaller): |
674 | request.sign_request( |
675 | OAuthSignatureMethod_PLAINTEXT(), self.consumer, |
676 | self.access_token) |
677 | - full_headers.update(request.to_header(OAUTH_REALM)) |
678 | + oauth_headers = request.to_header(OAUTH_REALM) |
679 | + full_headers.update({ |
680 | + six.ensure_str(key, encoding='ISO-8859-1'): |
681 | + six.ensure_str(value, encoding='ISO-8859-1') |
682 | + for key, value in oauth_headers.items()}) |
683 | if not self.handle_errors: |
684 | full_headers['X_Zope_handle_errors'] = 'False' |
685 | |
686 | @@ -692,7 +700,7 @@ def setupBrowser(auth=None): |
687 | string of the form 'Basic email:password' for an authenticated user. |
688 | :return: A `Browser` object. |
689 | """ |
690 | - browser = Browser(wsgi_app=AuthorizationMiddleware(wsgi_application)) |
691 | + browser = Browser() |
692 | # Set up our Browser objects with handleErrors set to False, since |
693 | # that gives a tracebacks instead of unhelpful error messages. |
694 | browser.handleErrors = False |
695 | diff --git a/lib/lp/testopenid/testing/helpers.py b/lib/lp/testopenid/testing/helpers.py |
696 | index c35c8f1..27f9855 100644 |
697 | --- a/lib/lp/testopenid/testing/helpers.py |
698 | +++ b/lib/lp/testopenid/testing/helpers.py |
699 | @@ -22,7 +22,6 @@ from six.moves.urllib.error import HTTPError |
700 | from zope.testbrowser.wsgi import Browser |
701 | |
702 | from lp.services.webapp import LaunchpadView |
703 | -from lp.testing.layers import wsgi_application |
704 | from lp.testopenid.interfaces.server import get_server_url |
705 | |
706 | |
707 | @@ -42,7 +41,7 @@ class ZopeFetcher(fetchers.HTTPFetcher): |
708 | """An `HTTPFetcher` based on zope.testbrowser.""" |
709 | |
710 | def fetch(self, url, body=None, headers=None): |
711 | - browser = Browser(wsgi_app=wsgi_application) |
712 | + browser = Browser() |
713 | if headers is not None: |
714 | for key, value in headers.items(): |
715 | browser.addHeader(key, value) |
716 | diff --git a/lib/zcml b/lib/zcml |
717 | new file mode 120000 |
718 | index 0000000..e0e5cba |
719 | --- /dev/null |
720 | +++ b/lib/zcml |
721 | @@ -0,0 +1 @@ |
722 | +../zcml |
723 | \ No newline at end of file |
724 | diff --git a/setup.py b/setup.py |
725 | index ed2da29..a0518bf 100644 |
726 | --- a/setup.py |
727 | +++ b/setup.py |
728 | @@ -243,7 +243,6 @@ setup( |
729 | 'zope.app.publication', |
730 | 'zope.app.publisher', |
731 | 'zope.app.server', |
732 | - 'zope.app.testing', |
733 | 'zope.app.wsgi', |
734 | 'zope.authentication', |
735 | 'zope.component[zcml]', |
736 | diff --git a/utilities/list-pages b/utilities/list-pages |
737 | index 823128d..7de2538 100755 |
738 | --- a/utilities/list-pages |
739 | +++ b/utilities/list-pages |
740 | @@ -47,7 +47,7 @@ import _pythonpath |
741 | from inspect import getmro |
742 | import os |
743 | |
744 | -from zope.app.testing.functional import FunctionalTestSetup |
745 | +from zope.app.wsgi.testlayer import BrowserLayer |
746 | from zope.browserpage.simpleviewclass import simple |
747 | from zope.component import adapter, getGlobalSiteManager |
748 | from zope.interface import directlyProvides, implementer |
749 | @@ -57,6 +57,7 @@ from lp.services.config import config |
750 | from lp.services.scripts import execute_zcml_for_scripts |
751 | from lp.services.webapp.interfaces import ICanonicalUrlData |
752 | from lp.services.webapp.publisher import canonical_url |
753 | +import zcml |
754 | |
755 | |
756 | ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) |
757 | @@ -69,7 +70,7 @@ def load_zcml(zopeless=False): |
758 | if zopeless: |
759 | execute_zcml_for_scripts() |
760 | else: |
761 | - FunctionalTestSetup(os.path.join(ROOT, 'zcml', 'webapp.zcml')).setUp() |
762 | + BrowserLayer(zcml, zcml_file='webapp.zcml').setUp() |
763 | |
764 | |
765 | def is_page_adapter(a): |
766 | diff --git a/zcml/__init__.py b/zcml/__init__.py |
767 | new file mode 100644 |
768 | index 0000000..e69de29 |
769 | --- /dev/null |
770 | +++ b/zcml/__init__.py |