Michael Merickel
2018-10-18 f28dbb0ba8d276fad10a3cd25e4d60b298702d83
commit | author | age
6a7914 1 import copy
35c5a3 2 import os
fbbb20 3 from contextlib import contextmanager
6a7914 4
0c29cf 5 from zope.interface import implementer, alsoProvides
deb0dc 6
0c29cf 7 from pyramid.interfaces import IRequest, ISession
17b8bc 8
0c29cf 9 from pyramid.compat import PY3, PYPY, class_types, text_
0c1c39 10
427a02 11 from pyramid.config import Configurator
a7b1a9 12 from pyramid.decorator import reify
d24055 13 from pyramid.path import caller_package
25c64c 14 from pyramid.response import _get_response_factory
b60bdb 15 from pyramid.registry import Registry
0c1c39 16
CM 17 from pyramid.security import (
18     Authenticated,
19     Everyone,
3c2f95 20     AuthenticationAPIMixin,
MR 21     AuthorizationAPIMixin,
0c29cf 22 )
0c1c39 23
0c29cf 24 from pyramid.threadlocal import get_current_registry, manager
0c1c39 25
330164 26 from pyramid.i18n import LocalizerRequestMixin
8fe57d 27 from pyramid.request import CallbackMethodsMixin
fb90f0 28 from pyramid.url import URLMethodsMixin
303abc 29 from pyramid.util import InstancePropertyMixin
7f1174 30 from pyramid.view import ViewMethodsMixin
303abc 31
13c923 32
7ec9e7 33 _marker = object()
e8b295 34
0c29cf 35
927acd 36 class DummyRootFactory(object):
CM 37     __parent__ = None
38     __name__ = None
0c29cf 39
e0f40e 40     def __init__(self, request):
CM 41         if 'bfg.routes.matchdict' in request:
42             self.__dict__.update(request['bfg.routes.matchdict'])
927acd 43
0c29cf 44
250c02 45 class DummySecurityPolicy(object):
a1a9fb 46     """ A standin for both an IAuthentication and IAuthorization policy """
0c29cf 47
MM 48     def __init__(
49         self,
50         userid=None,
51         groupids=(),
52         permissive=True,
53         remember_result=None,
54         forget_result=None,
55     ):
deb0dc 56         self.userid = userid
CM 57         self.groupids = groupids
a1a9fb 58         self.permissive = permissive
1273d5 59         if remember_result is None:
CM 60             remember_result = []
61         if forget_result is None:
62             forget_result = []
63         self.remember_result = remember_result
64         self.forget_result = forget_result
deb0dc 65
7ec9e7 66     def authenticated_userid(self, request):
deb0dc 67         return self.userid
CM 68
2526d8 69     def unauthenticated_userid(self, request):
deb0dc 70         return self.userid
CM 71
7ec9e7 72     def effective_principals(self, request):
deb0dc 73         effective_principals = [Everyone]
CM 74         if self.userid:
75             effective_principals.append(Authenticated)
76             effective_principals.append(self.userid)
77             effective_principals.extend(self.groupids)
78         return effective_principals
79
c7afe4 80     def remember(self, request, userid, **kw):
KOP 81         self.remembered = userid
1273d5 82         return self.remember_result
a1a9fb 83
7ec9e7 84     def forget(self, request):
1273d5 85         self.forgotten = True
CM 86         return self.forget_result
a1a9fb 87
CM 88     def permits(self, context, principals, permission):
89         return self.permissive
90
91     def principals_allowed_by_permission(self, context, permission):
7ec9e7 92         return self.effective_principals(None)
deb0dc 93
0c29cf 94
250c02 95 class DummyTemplateRenderer(object):
7d32df 96     """
CM 97     An instance of this class is returned from
d533b1 98     :meth:`pyramid.config.Configurator.testing_add_renderer`.  It has a
addf99 99     helper function (``assert_``) that makes it possible to make an
CM 100     assertion which compares data passed to the renderer by the view
101     function against expected key/value pairs.
7d32df 102     """
0c29cf 103
e02ba1 104     def __init__(self, string_response=''):
7d32df 105         self._received = {}
250c02 106         self._string_response = string_response
CM 107         self._implementation = MockTemplate(string_response)
108
109     # For in-the-wild test code that doesn't create its own renderer,
110     # but mutates our internals instead.  When all you read is the
111     # source code, *everything* is an API!
112     def _get_string_response(self):
113         return self._string_response
0c29cf 114
250c02 115     def _set_string_response(self, response):
CM 116         self._string_response = response
117         self._implementation.response = response
0c29cf 118
250c02 119     string_response = property(_get_string_response, _set_string_response)
CM 120
deb0dc 121     def implementation(self):
250c02 122         return self._implementation
c5f557 123
e46105 124     def __call__(self, kw, system=None):
250c02 125         if system:
CM 126             self._received.update(system)
7d32df 127         self._received.update(kw)
e02ba1 128         return self.string_response
deb0dc 129
7d32df 130     def __getattr__(self, k):
db0467 131         """ Backwards compatibility """
7d32df 132         val = self._received.get(k, _marker)
CM 133         if val is _marker:
250c02 134             val = self._implementation._received.get(k, _marker)
CM 135             if val is _marker:
136                 raise AttributeError(k)
7d32df 137         return val
CM 138
139     def assert_(self, **kw):
140         """ Accept an arbitrary set of assertion key/value pairs.  For
141         each assertion key/value pair assert that the renderer
aaedf5 142         (eg. :func:`pyramid.renderers.render_to_response`)
addf99 143         received the key with a value that equals the asserted
CM 144         value. If the renderer did not receive the key at all, or the
145         value received by the renderer doesn't match the assertion
146         value, raise an :exc:`AssertionError`."""
7d32df 147         for k, v in kw.items():
CM 148             myval = self._received.get(k, _marker)
149             if myval is _marker:
250c02 150                 myval = self._implementation._received.get(k, _marker)
CM 151                 if myval is _marker:
152                     raise AssertionError(
153                         'A value for key "%s" was not passed to the renderer'
0c29cf 154                         % k
MM 155                     )
c5f557 156
7d32df 157             if myval != v:
CM 158                 raise AssertionError(
0c29cf 159                     '\nasserted value for %s: %r\nactual value: %r'
MM 160                     % (k, v, myval)
161                 )
7d32df 162         return True
25c64c 163
7d32df 164
3e2f12 165 class DummyResource:
CM 166     """ A dummy :app:`Pyramid` :term:`resource` object."""
0c29cf 167
MM 168     def __init__(
169         self, __name__=None, __parent__=None, __provides__=None, **kw
170     ):
3e2f12 171         """ The resource's ``__name__`` attribute will be set to the
CM 172         value of the ``__name__`` argument, and the resource's
addf99 173         ``__parent__`` attribute will be set to the value of the
CM 174         ``__parent__`` argument.  If ``__provides__`` is specified, it
175         should be an interface object or tuple of interface objects
3e2f12 176         that will be attached to the resulting resource via
addf99 177         :func:`zope.interface.alsoProvides`. Any extra keywords passed
CM 178         in the ``kw`` argumnent will be set as direct attributes of
fec457 179         the resource object.
CM 180
181         .. note:: For backwards compatibility purposes, this class can also
182                   be imported as :class:`pyramid.testing.DummyModel`.
183
184         """
6a7914 185         self.__name__ = __name__
CM 186         self.__parent__ = __parent__
ab4e7b 187         if __provides__ is not None:
CM 188             alsoProvides(self, __provides__)
6a7914 189         self.kw = kw
CM 190         self.__dict__.update(**kw)
deb0dc 191         self.subs = {}
CM 192
193     def __setitem__(self, name, val):
a9a8a2 194         """ When the ``__setitem__`` method is called, the object
6a7914 195         passed in as ``val`` will be decorated with a ``__parent__``
3e2f12 196         attribute pointing at the dummy resource and a ``__name__``
a9a8a2 197         attribute that is the value of ``name``.  The value will then
3e2f12 198         be returned when dummy resource's ``__getitem__`` is called with
a9a8a2 199         the name ``name```."""
deb0dc 200         val.__name__ = name
CM 201         val.__parent__ = self
202         self.subs[name] = val
c5f557 203
deb0dc 204     def __getitem__(self, name):
a9a8a2 205         """ Return a named subobject (see ``__setitem__``)"""
deb0dc 206         ob = self.subs[name]
CM 207         return ob
6a7914 208
ee036d 209     def __delitem__(self, name):
CM 210         del self.subs[name]
211
40232e 212     def get(self, name, default=None):
CM 213         return self.subs.get(name, default)
214
e8ebc2 215     def values(self):
CM 216         """ Return the values set by __setitem__ """
217         return self.subs.values()
218
219     def items(self):
220         """ Return the items set by __setitem__ """
221         return self.subs.items()
222
223     def keys(self):
224         """ Return the keys set by __setitem__ """
225         return self.subs.keys()
226
40232e 227     __iter__ = keys
CM 228
42b1e9 229     def __nonzero__(self):
TS 230         return True
231
050b71 232     __bool__ = __nonzero__
CM 233
42b1e9 234     def __len__(self):
TS 235         return len(self.subs)
236
e8ebc2 237     def __contains__(self, name):
CM 238         return name in self.subs
c5f557 239
6a7914 240     def clone(self, __name__=_marker, __parent__=_marker, **kw):
3e2f12 241         """ Create a clone of the resource object.  If ``__name__`` or
addf99 242         ``__parent__`` arguments are passed, use these values to
CM 243         override the existing ``__name__`` or ``__parent__`` of the
3e2f12 244         resource.  If any extra keyword args are passed in via the ``kw``
b74cd4 245         argument, use these keywords to add to or override existing
3e2f12 246         resource keywords (attributes)."""
6a7914 247         oldkw = self.kw.copy()
CM 248         oldkw.update(kw)
249         inst = self.__class__(self.__name__, self.__parent__, **oldkw)
250         inst.subs = copy.deepcopy(self.subs)
251         if __name__ is not _marker:
252             inst.__name__ = __name__
253         if __parent__ is not _marker:
254             inst.__parent__ = __parent__
255         return inst
3e2f12 256
0c29cf 257
MM 258 DummyModel = DummyResource  # b/w compat (forever)
259
6a7914 260
3b7334 261 @implementer(ISession)
51b2e8 262 class DummySession(dict):
CM 263     created = None
264     new = True
0c29cf 265
51b2e8 266     def changed(self):
CM 267         pass
268
269     def invalidate(self):
270         self.clear()
271
272     def flash(self, msg, queue='', allow_duplicate=True):
273         storage = self.setdefault('_f_' + queue, [])
274         if allow_duplicate or (msg not in storage):
275             storage.append(msg)
276
277     def pop_flash(self, queue=''):
278         storage = self.pop('_f_' + queue, [])
279         return storage
280
281     def peek_flash(self, queue=''):
282         storage = self.get('_f_' + queue, [])
283         return storage
284
285     def new_csrf_token(self):
183804 286         token = text_('0123456789012345678901234567890123456789')
51b2e8 287         self['_csrft_'] = token
CM 288         return token
289
290     def get_csrf_token(self):
4d2003 291         token = self.get('_csrft_', None)
RB 292         if token is None:
293             token = self.new_csrf_token()
294         return token
295
0c29cf 296
3b7334 297 @implementer(IRequest)
0184b5 298 class DummyRequest(
CM 299     URLMethodsMixin,
300     CallbackMethodsMixin,
301     InstancePropertyMixin,
302     LocalizerRequestMixin,
303     AuthenticationAPIMixin,
304     AuthorizationAPIMixin,
7f1174 305     ViewMethodsMixin,
0c29cf 306 ):
097d37 307     """ A DummyRequest object (incompletely) imitates a :term:`request` object.
c5f557 308
8b1f6e 309     The ``params``, ``environ``, ``headers``, ``path``, and
22d325 310     ``cookies`` arguments correspond to their :term:`WebOb`
8b1f6e 311     equivalents.
6801b5 312
TS 313     The ``post`` argument,  if passed, populates the request's
314     ``POST`` attribute, but *not* ``params``, in order to allow testing
315     that the app accepts data for a given view only from POST requests.
316     This argument also sets ``self.method`` to "POST".
317
318     Extra keyword arguments are assigned as attributes of the request
319     itself.
3e2f12 320
097d37 321     Note that DummyRequest does not have complete fidelity with a "real"
CM 322     request.  For example, by default, the DummyRequest ``GET`` and ``POST``
323     attributes are of type ``dict``, unlike a normal Request's GET and POST,
324     which are of type ``MultiDict``. If your code uses the features of
07cb8f 325     MultiDict, you should either use a real :class:`pyramid.request.Request`
097d37 326     or adapt your DummyRequest by replacing the attributes with ``MultiDict``
CM 327     instances.
328
329     Other similar incompatibilities exist.  If you need all the features of
330     a Request, use the :class:`pyramid.request.Request` class itself rather
331     than this class while writing tests.
6801b5 332     """
0c29cf 333
516729 334     method = 'GET'
CM 335     application_url = 'http://example.com'
336     host = 'example.com:80'
767e44 337     domain = 'example.com'
2dc0b5 338     content_length = 0
c5f557 339     query_string = ''
f2681a 340     charset = 'UTF-8'
0a0edf 341     script_name = ''
a7b1a9 342     _registry = None
849196 343     request_iface = IRequest
a7b1a9 344
0c29cf 345     def __init__(
MM 346         self,
347         params=None,
348         environ=None,
349         headers=None,
350         path='/',
351         cookies=None,
352         post=None,
353         **kw
354     ):
a9a8a2 355         if environ is None:
CM 356             environ = {}
357         if params is None:
358             params = {}
359         if headers is None:
360             headers = {}
516729 361         if cookies is None:
CM 362             cookies = {}
a9a8a2 363         self.environ = environ
CM 364         self.headers = headers
168c10 365         self.params = params
516729 366         self.cookies = cookies
927acd 367         self.matchdict = {}
168c10 368         self.GET = params
6801b5 369         if post is not None:
TS 370             self.method = 'POST'
371             self.POST = post
372         else:
168c10 373             self.POST = params
a9a8a2 374         self.host_url = self.application_url
CM 375         self.path_url = self.application_url
516729 376         self.url = self.application_url
a9a8a2 377         self.path = path
CM 378         self.path_info = path
379         self.script_name = ''
380         self.path_qs = ''
381         self.body = ''
516729 382         self.view_name = ''
1cd598 383         self.subpath = ()
CM 384         self.traversed = ()
385         self.virtual_root_path = ()
516729 386         self.context = None
1cd598 387         self.root = None
CM 388         self.virtual_root = None
0c29cf 389         self.marshalled = params  # repoze.monty
51b2e8 390         self.session = DummySession()
a9a8a2 391         self.__dict__.update(kw)
844e98 392
a7b1a9 393     def _get_registry(self):
CM 394         if self._registry is None:
395             return get_current_registry()
396         return self._registry
397
398     def _set_registry(self, registry):
399         self._registry = registry
400
401     def _del_registry(self):
402         self._registry = None
403
404     registry = property(_get_registry, _set_registry, _del_registry)
405
406     @reify
407     def response(self):
25c64c 408         f = _get_response_factory(self.registry)
32cb80 409         return f(self)
8af47b 410
0c29cf 411
bce463 412 have_zca = True
25c64c 413
bce463 414
0c29cf 415 def setUp(
MM 416     registry=None,
417     request=None,
418     hook_zca=True,
419     autocommit=True,
420     settings=None,
421     package=None,
422 ):
a7e6f2 423     """
fd5ae9 424     Set :app:`Pyramid` registry and request thread locals for the
addf99 425     duration of a single unit test.
d0b398 426
7ac5aa 427     Use this function in the ``setUp`` method of a unittest test case
fe399b 428     which directly or indirectly uses:
CM 429
aff443 430     - any method of the :class:`pyramid.config.Configurator`
addf99 431       object returned by this function.
CM 432
c81aad 433     - the :func:`pyramid.threadlocal.get_current_registry` or
CM 434       :func:`pyramid.threadlocal.get_current_request` functions.
d0b398 435
d533b1 436     If you use the ``get_current_*`` functions (or call :app:`Pyramid` code
CM 437     that uses these functions) without calling ``setUp``,
438     :func:`pyramid.threadlocal.get_current_registry` will return a *global*
439     :term:`application registry`, which may cause unit tests to not be
440     isolated with respect to registrations they perform.
d0b398 441
CM 442     If the ``registry`` argument is ``None``, a new empty
addf99 443     :term:`application registry` will be created (an instance of the
c81aad 444     :class:`pyramid.registry.Registry` class).  If the ``registry``
addf99 445     argument is not ``None``, the value passed in should be an
c81aad 446     instance of the :class:`pyramid.registry.Registry` class or a
addf99 447     suitable testing analogue.
CM 448
449     After ``setUp`` is finished, the registry returned by the
8a7d6c 450     :func:`pyramid.threadlocal.get_current_registry` function will
d0b398 451     be the passed (or constructed) registry until
c81aad 452     :func:`pyramid.testing.tearDown` is called (or
CM 453     :func:`pyramid.testing.setUp` is called again) .
d0b398 454
addf99 455     If the ``hook_zca`` argument is ``True``, ``setUp`` will attempt
CM 456     to perform the operation ``zope.component.getSiteManager.sethook(
c81aad 457     pyramid.threadlocal.get_current_registry)``, which will cause
d0b398 458     the :term:`Zope Component Architecture` global API
addf99 459     (e.g. :func:`zope.component.getSiteManager`,
CM 460     :func:`zope.component.getAdapter`, and so on) to use the registry
461     constructed by ``setUp`` as the value it returns from
462     :func:`zope.component.getSiteManager`.  If the
463     :mod:`zope.component` package cannot be imported, or if
464     ``hook_zca`` is ``False``, the hook will not be set.
465
d24055 466     If ``settings`` is not ``None``, it must be a dictionary representing the
2a13b0 467     values passed to a Configurator as its ``settings=`` argument.
d24055 468
MM 469     If ``package`` is ``None`` it will be set to the caller's package. The
470     ``package`` setting in the :class:`pyramid.config.Configurator` will
471     affect any relative imports made via
472     :meth:`pyramid.config.Configurator.include` or
473     :meth:`pyramid.config.Configurator.maybe_dotted`.
2a13b0 474
addf99 475     This function returns an instance of the
aff443 476     :class:`pyramid.config.Configurator` class, which can be
addf99 477     used for further configuration to set up an environment suitable
CM 478     for a unit or integration test.  The ``registry`` attribute
479     attached to the Configurator instance represents the 'current'
480     :term:`application registry`; the same registry will be returned
c81aad 481     by :func:`pyramid.threadlocal.get_current_registry` during the
addf99 482     execution of the test.
d322ac 483     """
cbfafb 484     manager.clear()
d0b398 485     if registry is None:
CM 486         registry = Registry('testing')
d24055 487     if package is None:
MM 488         package = caller_package()
0c29cf 489     config = Configurator(
MM 490         registry=registry, autocommit=autocommit, package=package
491     )
2a13b0 492     if settings is None:
CM 493         settings = {}
494     if getattr(registry, 'settings', None) is None:
495         config._set_settings(settings)
250c02 496     if hasattr(registry, 'registerUtility'):
CM 497         # Sometimes nose calls us with a non-registry object because
498         # it thinks this function is module test setup.  Likewise,
499         # someone may be passing us an esoteric "dummy" registry, and
500         # the below won't succeed if it doesn't have a registerUtility
501         # method.
121f45 502         config.add_default_response_adapters()
b7e92d 503         config.add_default_renderers()
121f45 504         config.add_default_accept_view_order()
a00621 505         config.add_default_view_predicates()
ceb1f2 506         config.add_default_view_derivers()
9c8ec5 507         config.add_default_route_predicates()
35b0e3 508         config.add_default_tweens()
4b3603 509         config.add_default_security()
c0d4a1 510     config.commit()
bce463 511     global have_zca
CM 512     try:
513         have_zca and hook_zca and config.hook_zca()
0c29cf 514     except ImportError:  # pragma: no cover
338cb9 515         # (dont choke on not being able to import z.component)
bce463 516         have_zca = False
bc857e 517     config.begin(request=request)
addf99 518     return config
0c29cf 519
d0b398 520
CM 521 def tearDown(unhook_zca=True):
b9b3b1 522     """Undo the effects of :func:`pyramid.testing.setUp`.  Use this
addf99 523     function in the ``tearDown`` method of a unit test that uses
c81aad 524     :func:`pyramid.testing.setUp` in its ``setUp`` method.
d0b398 525
CM 526     If the ``unhook_zca`` argument is ``True`` (the default), call
addf99 527     :func:`zope.component.getSiteManager.reset`.  This undoes the
688a93 528     action of :func:`pyramid.testing.setUp` when called with the
addf99 529     argument ``hook_zca=True``.  If :mod:`zope.component` cannot be
688a93 530     imported, ``unhook_zca`` is set to ``False``.
d0b398 531     """
bce463 532     global have_zca
CM 533     if unhook_zca and have_zca:
7696aa 534         try:
CM 535             from zope.component import getSiteManager
0c29cf 536
7696aa 537             getSiteManager.reset()
0c29cf 538         except ImportError:  # pragma: no cover
bce463 539             have_zca = False
d0b398 540     info = manager.pop()
CM 541     manager.clear()
542     if info is not None:
bc857e 543         registry = info['registry']
CM 544         if hasattr(registry, '__init__') and hasattr(registry, '__name__'):
7ac5aa 545             try:
bc857e 546                 registry.__init__(registry.__name__)
7ac5aa 547             except TypeError:
99fc64 548                 # calling __init__ is largely for the benefit of
CM 549                 # people who want to use the global ZCA registry;
550                 # however maybe somebody's using a registry we don't
551                 # understand, let's not blow up
7ac5aa 552                 pass
17b8bc 553
0c29cf 554
d0b398 555 def cleanUp(*arg, **kw):
c0d0a3 556     """ An alias for :func:`pyramid.testing.setUp`. """
d24055 557     package = kw.get('package', None)
MM 558     if package is None:
559         package = caller_package()
560         kw['package'] = package
addf99 561     return setUp(*arg, **kw)
0c29cf 562
59c5df 563
250c02 564 class DummyRendererFactory(object):
CM 565     """ Registered by
d533b1 566     :meth:`pyramid.config.Configurator.testing_add_renderer` as
250c02 567     a dummy renderer factory.  The indecision about what to use as a
CM 568     key (a spec vs. a relative name) is caused by test suites in the
569     wild believing they can register either.  The ``factory`` argument
570     passed to this constructor is usually the *real* template renderer
571     factory, found when ``testing_add_renderer`` is called."""
0c29cf 572
250c02 573     def __init__(self, name, factory):
CM 574         self.name = name
0c29cf 575         self.factory = factory  # the "real" renderer factory reg'd previously
250c02 576         self.renderers = {}
CM 577
578     def add(self, spec, renderer):
579         self.renderers[spec] = renderer
580         if ':' in spec:
581             package, relative = spec.split(':', 1)
582             self.renderers[relative] = renderer
583
3d9dd0 584     def __call__(self, info):
f5fa3f 585         spec = info.name
250c02 586         renderer = self.renderers.get(spec)
CM 587         if renderer is None:
588             if ':' in spec:
589                 package, relative = spec.split(':', 1)
590                 renderer = self.renderers.get(relative)
591             if renderer is None:
592                 if self.factory:
3d9dd0 593                     renderer = self.factory(info)
250c02 594                 else:
0c29cf 595                     raise KeyError(
MM 596                         'No testing renderer registered for %r' % spec
597                     )
250c02 598         return renderer
c5f557 599
CR 600
250c02 601 class MockTemplate(object):
CM 602     def __init__(self, response):
603         self._received = {}
604         self.response = response
0c29cf 605
250c02 606     def __getattr__(self, attrname):
CM 607         return self
0c29cf 608
250c02 609     def __getitem__(self, attrname):
CM 610         return self
0c29cf 611
250c02 612     def __call__(self, *arg, **kw):
CM 613         self._received.update(kw)
614         return self.response
35c5a3 615
0c29cf 616
MM 617 def skip_on(*platforms):  # pragma: no  cover
a84e17 618     skip = False
CM 619     for platform in platforms:
620         if skip_on.os_name.startswith(platform):
621             skip = True
160865 622         if platform == 'pypy' and PYPY:
a84e17 623             skip = True
160865 624         if platform == 'py3' and PY3:
a84e17 625             skip = True
25c64c 626
35c5a3 627     def decorator(func):
a84e17 628         if isinstance(func, class_types):
25c64c 629             if skip:
JA 630                 return None
631             else:
632                 return func
a84e17 633         else:
0c29cf 634
a84e17 635             def wrapper(*args, **kw):
CM 636                 if skip:
35c5a3 637                     return
a84e17 638                 return func(*args, **kw)
0c29cf 639
a84e17 640             wrapper.__name__ = func.__name__
CM 641             wrapper.__doc__ = func.__doc__
642             return wrapper
0c29cf 643
35c5a3 644     return decorator
0c29cf 645
MM 646
647 skip_on.os_name = os.name  # for testing
648
fbbb20 649
BS 650 @contextmanager
0c29cf 651 def testConfig(
MM 652     registry=None, request=None, hook_zca=True, autocommit=True, settings=None
653 ):
fbbb20 654     """Returns a context manager for test set up.
BS 655
aaedf5 656     This context manager calls :func:`pyramid.testing.setUp` when
fbbb20 657     entering and :func:`pyramid.testing.tearDown` when exiting.
BS 658
aaedf5 659     All arguments are passed directly to :func:`pyramid.testing.setUp`.
fbbb20 660     If the ZCA is hooked, it will always be un-hooked in tearDown.
BS 661
662     This context manager allows you to write test code like this:
663
664     .. code-block:: python
665         :linenos:
666
667         with testConfig() as config:
668             config.add_route('bar', '/bar/{id}')
669             req = DummyRequest()
b2fb29 670             resp = myview(req)
fbbb20 671     """
0c29cf 672     config = setUp(
MM 673         registry=registry,
674         request=request,
675         hook_zca=hook_zca,
676         autocommit=autocommit,
677         settings=settings,
678     )
fbbb20 679     try:
BS 680         yield config
681     finally:
682         tearDown(unhook_zca=hook_zca)