Unified test context decorators.
Thanks to Tim for the review.
This commit is contained in:
parent
a08fda2111
commit
4ccf7154c3
|
@ -5,7 +5,7 @@ import time
|
||||||
import warnings
|
import warnings
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from unittest import skipIf, skipUnless
|
from unittest import TestCase, skipIf, skipUnless
|
||||||
from xml.dom.minidom import Node, parseString
|
from xml.dom.minidom import Node, parseString
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
@ -20,7 +20,7 @@ from django.template import Template
|
||||||
from django.test.signals import setting_changed, template_rendered
|
from django.test.signals import setting_changed, template_rendered
|
||||||
from django.urls import get_script_prefix, set_script_prefix
|
from django.urls import get_script_prefix, set_script_prefix
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.decorators import ContextDecorator
|
from django.utils.decorators import available_attrs
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
from django.utils.translation import deactivate
|
from django.utils.translation import deactivate
|
||||||
|
|
||||||
|
@ -151,45 +151,81 @@ def get_runner(settings, test_runner_class=None):
|
||||||
return test_runner
|
return test_runner
|
||||||
|
|
||||||
|
|
||||||
class override_settings(object):
|
class TestContextDecorator(object):
|
||||||
"""
|
"""
|
||||||
Acts as either a decorator, or a context manager. If it's a decorator it
|
A base class that can either be used as a context manager during tests
|
||||||
|
or as a test function or unittest.TestCase subclass decorator to perform
|
||||||
|
temporary alterations.
|
||||||
|
|
||||||
|
`attr_name`: attribute assigned the return value of enable() if used as
|
||||||
|
a class decorator.
|
||||||
|
|
||||||
|
`kwarg_name`: keyword argument passing the return value of enable() if
|
||||||
|
used as a function decorator.
|
||||||
|
"""
|
||||||
|
def __init__(self, attr_name=None, kwarg_name=None):
|
||||||
|
self.attr_name = attr_name
|
||||||
|
self.kwarg_name = kwarg_name
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self.enable()
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, traceback):
|
||||||
|
self.disable()
|
||||||
|
|
||||||
|
def decorate_class(self, cls):
|
||||||
|
if issubclass(cls, TestCase):
|
||||||
|
decorated_setUp = cls.setUp
|
||||||
|
decorated_tearDown = cls.tearDown
|
||||||
|
|
||||||
|
def setUp(inner_self):
|
||||||
|
context = self.enable()
|
||||||
|
if self.attr_name:
|
||||||
|
setattr(inner_self, self.attr_name, context)
|
||||||
|
decorated_setUp(inner_self)
|
||||||
|
|
||||||
|
def tearDown(inner_self):
|
||||||
|
decorated_tearDown(inner_self)
|
||||||
|
self.disable()
|
||||||
|
|
||||||
|
cls.setUp = setUp
|
||||||
|
cls.tearDown = tearDown
|
||||||
|
return cls
|
||||||
|
raise TypeError('Can only decorate subclasses of unittest.TestCase')
|
||||||
|
|
||||||
|
def decorate_callable(self, func):
|
||||||
|
@wraps(func, assigned=available_attrs(func))
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
with self as context:
|
||||||
|
if self.kwarg_name:
|
||||||
|
kwargs[self.kwarg_name] = context
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def __call__(self, decorated):
|
||||||
|
if isinstance(decorated, type):
|
||||||
|
return self.decorate_class(decorated)
|
||||||
|
elif callable(decorated):
|
||||||
|
return self.decorate_callable(decorated)
|
||||||
|
raise TypeError('Cannot decorate object of type %s' % type(decorated))
|
||||||
|
|
||||||
|
|
||||||
|
class override_settings(TestContextDecorator):
|
||||||
|
"""
|
||||||
|
Acts as either a decorator or a context manager. If it's a decorator it
|
||||||
takes a function and returns a wrapped function. If it's a contextmanager
|
takes a function and returns a wrapped function. If it's a contextmanager
|
||||||
it's used with the ``with`` statement. In either event entering/exiting
|
it's used with the ``with`` statement. In either event entering/exiting
|
||||||
are called before and after, respectively, the function/block is executed.
|
are called before and after, respectively, the function/block is executed.
|
||||||
"""
|
"""
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.options = kwargs
|
self.options = kwargs
|
||||||
|
super(override_settings, self).__init__()
|
||||||
def __enter__(self):
|
|
||||||
self.enable()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
|
||||||
self.disable()
|
|
||||||
|
|
||||||
def __call__(self, test_func):
|
|
||||||
from django.test import SimpleTestCase
|
|
||||||
if isinstance(test_func, type):
|
|
||||||
if not issubclass(test_func, SimpleTestCase):
|
|
||||||
raise Exception(
|
|
||||||
"Only subclasses of Django SimpleTestCase can be decorated "
|
|
||||||
"with override_settings")
|
|
||||||
self.save_options(test_func)
|
|
||||||
return test_func
|
|
||||||
else:
|
|
||||||
@wraps(test_func)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
with self:
|
|
||||||
return test_func(*args, **kwargs)
|
|
||||||
return inner
|
|
||||||
|
|
||||||
def save_options(self, test_func):
|
|
||||||
if test_func._overridden_settings is None:
|
|
||||||
test_func._overridden_settings = self.options
|
|
||||||
else:
|
|
||||||
# Duplicate dict to prevent subclasses from altering their parent.
|
|
||||||
test_func._overridden_settings = dict(
|
|
||||||
test_func._overridden_settings, **self.options)
|
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
# Keep this code at the beginning to leave the settings unchanged
|
# Keep this code at the beginning to leave the settings unchanged
|
||||||
|
@ -219,6 +255,23 @@ class override_settings(object):
|
||||||
setting_changed.send(sender=settings._wrapped.__class__,
|
setting_changed.send(sender=settings._wrapped.__class__,
|
||||||
setting=key, value=new_value, enter=False)
|
setting=key, value=new_value, enter=False)
|
||||||
|
|
||||||
|
def save_options(self, test_func):
|
||||||
|
if test_func._overridden_settings is None:
|
||||||
|
test_func._overridden_settings = self.options
|
||||||
|
else:
|
||||||
|
# Duplicate dict to prevent subclasses from altering their parent.
|
||||||
|
test_func._overridden_settings = dict(
|
||||||
|
test_func._overridden_settings, **self.options)
|
||||||
|
|
||||||
|
def decorate_class(self, cls):
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
if not issubclass(cls, SimpleTestCase):
|
||||||
|
raise ValueError(
|
||||||
|
"Only subclasses of Django SimpleTestCase can be decorated "
|
||||||
|
"with override_settings")
|
||||||
|
self.save_options(cls)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
class modify_settings(override_settings):
|
class modify_settings(override_settings):
|
||||||
"""
|
"""
|
||||||
|
@ -233,6 +286,7 @@ class modify_settings(override_settings):
|
||||||
else:
|
else:
|
||||||
assert not args
|
assert not args
|
||||||
self.operations = list(kwargs.items())
|
self.operations = list(kwargs.items())
|
||||||
|
super(override_settings, self).__init__()
|
||||||
|
|
||||||
def save_options(self, test_func):
|
def save_options(self, test_func):
|
||||||
if test_func._modified_settings is None:
|
if test_func._modified_settings is None:
|
||||||
|
@ -267,28 +321,29 @@ class modify_settings(override_settings):
|
||||||
super(modify_settings, self).enable()
|
super(modify_settings, self).enable()
|
||||||
|
|
||||||
|
|
||||||
def override_system_checks(new_checks, deployment_checks=None):
|
class override_system_checks(TestContextDecorator):
|
||||||
""" Acts as a decorator. Overrides list of registered system checks.
|
"""
|
||||||
|
Acts as a decorator. Overrides list of registered system checks.
|
||||||
Useful when you override `INSTALLED_APPS`, e.g. if you exclude `auth` app,
|
Useful when you override `INSTALLED_APPS`, e.g. if you exclude `auth` app,
|
||||||
you also need to exclude its system checks. """
|
you also need to exclude its system checks.
|
||||||
|
"""
|
||||||
|
def __init__(self, new_checks, deployment_checks=None):
|
||||||
|
from django.core.checks.registry import registry
|
||||||
|
self.registry = registry
|
||||||
|
self.new_checks = new_checks
|
||||||
|
self.deployment_checks = deployment_checks
|
||||||
|
super(override_system_checks, self).__init__()
|
||||||
|
|
||||||
from django.core.checks.registry import registry
|
def enable(self):
|
||||||
|
self.old_checks = self.registry.registered_checks
|
||||||
|
self.registry.registered_checks = self.new_checks
|
||||||
|
self.old_deployment_checks = self.registry.deployment_checks
|
||||||
|
if self.deployment_checks is not None:
|
||||||
|
self.registry.deployment_checks = self.deployment_checks
|
||||||
|
|
||||||
def outer(test_func):
|
def disable(self):
|
||||||
@wraps(test_func)
|
self.registry.registered_checks = self.old_checks
|
||||||
def inner(*args, **kwargs):
|
self.registry.deployment_checks = self.old_deployment_checks
|
||||||
old_checks = registry.registered_checks
|
|
||||||
registry.registered_checks = new_checks
|
|
||||||
old_deployment_checks = registry.deployment_checks
|
|
||||||
if deployment_checks is not None:
|
|
||||||
registry.deployment_checks = deployment_checks
|
|
||||||
try:
|
|
||||||
return test_func(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
registry.registered_checks = old_checks
|
|
||||||
registry.deployment_checks = old_deployment_checks
|
|
||||||
return inner
|
|
||||||
return outer
|
|
||||||
|
|
||||||
|
|
||||||
def compare_xml(want, got):
|
def compare_xml(want, got):
|
||||||
|
@ -428,40 +483,22 @@ class CaptureQueriesContext(object):
|
||||||
self.final_queries = len(self.connection.queries_log)
|
self.final_queries = len(self.connection.queries_log)
|
||||||
|
|
||||||
|
|
||||||
class ignore_warnings(object):
|
class ignore_warnings(TestContextDecorator):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.ignore_kwargs = kwargs
|
self.ignore_kwargs = kwargs
|
||||||
if 'message' in self.ignore_kwargs or 'module' in self.ignore_kwargs:
|
if 'message' in self.ignore_kwargs or 'module' in self.ignore_kwargs:
|
||||||
self.filter_func = warnings.filterwarnings
|
self.filter_func = warnings.filterwarnings
|
||||||
else:
|
else:
|
||||||
self.filter_func = warnings.simplefilter
|
self.filter_func = warnings.simplefilter
|
||||||
|
super(ignore_warnings, self).__init__()
|
||||||
|
|
||||||
def __call__(self, decorated):
|
def enable(self):
|
||||||
if isinstance(decorated, type):
|
self.catch_warnings = warnings.catch_warnings()
|
||||||
# A class is decorated
|
self.catch_warnings.__enter__()
|
||||||
saved_setUp = decorated.setUp
|
self.filter_func('ignore', **self.ignore_kwargs)
|
||||||
saved_tearDown = decorated.tearDown
|
|
||||||
|
|
||||||
def setUp(inner_self):
|
def disable(self):
|
||||||
self.catch_warnings = warnings.catch_warnings()
|
self.catch_warnings.__exit__(*sys.exc_info())
|
||||||
self.catch_warnings.__enter__()
|
|
||||||
self.filter_func('ignore', **self.ignore_kwargs)
|
|
||||||
saved_setUp(inner_self)
|
|
||||||
|
|
||||||
def tearDown(inner_self):
|
|
||||||
saved_tearDown(inner_self)
|
|
||||||
self.catch_warnings.__exit__(*sys.exc_info())
|
|
||||||
|
|
||||||
decorated.setUp = setUp
|
|
||||||
decorated.tearDown = tearDown
|
|
||||||
return decorated
|
|
||||||
else:
|
|
||||||
@wraps(decorated)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
with warnings.catch_warnings():
|
|
||||||
self.filter_func('ignore', **self.ignore_kwargs)
|
|
||||||
return decorated(*args, **kwargs)
|
|
||||||
return inner
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
|
@ -610,23 +647,20 @@ def require_jinja2(test_func):
|
||||||
return test_func
|
return test_func
|
||||||
|
|
||||||
|
|
||||||
class ScriptPrefix(ContextDecorator):
|
class override_script_prefix(TestContextDecorator):
|
||||||
def __enter__(self):
|
|
||||||
set_script_prefix(self.prefix)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, traceback):
|
|
||||||
set_script_prefix(self.old_prefix)
|
|
||||||
|
|
||||||
def __init__(self, prefix):
|
|
||||||
self.prefix = prefix
|
|
||||||
self.old_prefix = get_script_prefix()
|
|
||||||
|
|
||||||
|
|
||||||
def override_script_prefix(prefix):
|
|
||||||
"""
|
"""
|
||||||
Decorator or context manager to temporary override the script prefix.
|
Decorator or context manager to temporary override the script prefix.
|
||||||
"""
|
"""
|
||||||
return ScriptPrefix(prefix)
|
def __init__(self, prefix):
|
||||||
|
self.prefix = prefix
|
||||||
|
super(override_script_prefix, self).__init__()
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
self.old_prefix = get_script_prefix()
|
||||||
|
set_script_prefix(self.prefix)
|
||||||
|
|
||||||
|
def disable(self):
|
||||||
|
set_script_prefix(self.old_prefix)
|
||||||
|
|
||||||
|
|
||||||
class LoggingCaptureMixin(object):
|
class LoggingCaptureMixin(object):
|
||||||
|
@ -644,7 +678,7 @@ class LoggingCaptureMixin(object):
|
||||||
self.logger.handlers[0].stream = self.old_stream
|
self.logger.handlers[0].stream = self.old_stream
|
||||||
|
|
||||||
|
|
||||||
class isolate_apps(object):
|
class isolate_apps(TestContextDecorator):
|
||||||
"""
|
"""
|
||||||
Act as either a decorator or a context manager to register models defined
|
Act as either a decorator or a context manager to register models defined
|
||||||
in its wrapped context to an isolated registry.
|
in its wrapped context to an isolated registry.
|
||||||
|
@ -657,14 +691,13 @@ class isolate_apps(object):
|
||||||
`attr_name`: attribute assigned the isolated registry if used as a class
|
`attr_name`: attribute assigned the isolated registry if used as a class
|
||||||
decorator.
|
decorator.
|
||||||
|
|
||||||
`kwarg_name`: keyword argument passing the isolated registry to the
|
`kwarg_name`: keyword argument passing the isolated registry if used as a
|
||||||
decorated method.
|
function decorator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *installed_apps, **kwargs):
|
def __init__(self, *installed_apps, **kwargs):
|
||||||
self.installed_apps = installed_apps
|
self.installed_apps = installed_apps
|
||||||
self.attr_name = kwargs.pop('attr_name', None)
|
super(isolate_apps, self).__init__(**kwargs)
|
||||||
self.kwarg_name = kwargs.pop('kwarg_name', None)
|
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.old_apps = Options.default_apps
|
self.old_apps = Options.default_apps
|
||||||
|
@ -674,37 +707,3 @@ class isolate_apps(object):
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
setattr(Options, 'default_apps', self.old_apps)
|
setattr(Options, 'default_apps', self.old_apps)
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
return self.enable()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, traceback):
|
|
||||||
self.disable()
|
|
||||||
|
|
||||||
def __call__(self, decorated):
|
|
||||||
if isinstance(decorated, type):
|
|
||||||
# A class is decorated
|
|
||||||
decorated_setUp = decorated.setUp
|
|
||||||
decorated_tearDown = decorated.tearDown
|
|
||||||
|
|
||||||
def setUp(inner_self):
|
|
||||||
apps = self.enable()
|
|
||||||
if self.attr_name:
|
|
||||||
setattr(inner_self, self.attr_name, apps)
|
|
||||||
decorated_setUp(inner_self)
|
|
||||||
|
|
||||||
def tearDown(inner_self):
|
|
||||||
decorated_tearDown(inner_self)
|
|
||||||
self.disable()
|
|
||||||
|
|
||||||
decorated.setUp = setUp
|
|
||||||
decorated.tearDown = tearDown
|
|
||||||
return decorated
|
|
||||||
else:
|
|
||||||
@wraps(decorated)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
with self as apps:
|
|
||||||
if self.kwarg_name:
|
|
||||||
kwargs[self.kwarg_name] = apps
|
|
||||||
return decorated(*args, **kwargs)
|
|
||||||
return inner
|
|
||||||
|
|
Loading…
Reference in New Issue