Merge ~cjwatson/launchpad:remove-zope.app.testing into launchpad:master

Proposed by Colin Watson
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)
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.testlayer.ZCMLFileLayer and zope.testbrowser.wsgi.Layer. What used to be in wsgi_application is now pulled apart into a few pieces of middleware around zope.app.wsgi.WSGIPublisherApplication.

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

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.
 * LaunchpadWebServiceCaller.addHeadersTo makes sure that the headers it adds are encoded in WSGI-friendly ways.
 * A couple of tests in TestBuilderHistoryView need to clear the permission cache between creating test objects and doing privacy-related tests on them; I haven't found exactly what was doing this before, but I think it may have been an accidental side-effect of something in the guts of ZODB.

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/buildmaster/browser/tests/test_builder_views.py b/lib/lp/buildmaster/browser/tests/test_builder_views.py
2index 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(
29diff --git a/lib/lp/services/profile/profiling.txt b/lib/lp/services/profile/profiling.txt
30index 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
54diff --git a/lib/lp/services/webapp/tests/test_authutility.py b/lib/lp/services/webapp/tests/test_authutility.py
55index 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):
101diff --git a/lib/lp/services/webapp/tests/test_error.py b/lib/lp/services/webapp/tests/test_error.py
102index 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)
139diff --git a/lib/lp/services/webapp/tests/test_login.py b/lib/lp/services/webapp/tests/test_login.py
140index 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',
160diff --git a/lib/lp/services/webapp/tests/test_notifications.py b/lib/lp/services/webapp/tests/test_notifications.py
161index 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():
195diff --git a/lib/lp/services/worlddata/tests/test_helpers.py b/lib/lp/services/worlddata/tests/test_helpers.py
196index 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
209diff --git a/lib/lp/testing/browser.py b/lib/lp/testing/browser.py
210index 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):
251diff --git a/lib/lp/testing/layers.py b/lib/lp/testing/layers.py
252index 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'))
606diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
607index 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
695diff --git a/lib/lp/testopenid/testing/helpers.py b/lib/lp/testopenid/testing/helpers.py
696index 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)
716diff --git a/lib/zcml b/lib/zcml
717new file mode 120000
718index 0000000..e0e5cba
719--- /dev/null
720+++ b/lib/zcml
721@@ -0,0 +1 @@
722+../zcml
723\ No newline at end of file
724diff --git a/setup.py b/setup.py
725index 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]',
736diff --git a/utilities/list-pages b/utilities/list-pages
737index 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):
766diff --git a/zcml/__init__.py b/zcml/__init__.py
767new file mode 100644
768index 0000000..e69de29
769--- /dev/null
770+++ b/zcml/__init__.py

Subscribers

People subscribed via source and target branches

to status/vote changes: