mirror of
https://github.com/django/django.git
synced 2025-03-31 19:46:42 +00:00
Fixed #29467 -- Made override_settings handle errors in setting_changed signal receivers.
This commit is contained in:
parent
cebbcaa1ba
commit
c6238bf02b
@ -382,6 +382,8 @@ class override_settings(TestContextDecorator):
|
|||||||
with the ``with`` statement. In either event, entering/exiting are called
|
with the ``with`` statement. In either event, entering/exiting are called
|
||||||
before and after, respectively, the function/block is executed.
|
before and after, respectively, the function/block is executed.
|
||||||
"""
|
"""
|
||||||
|
enable_exception = None
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.options = kwargs
|
self.options = kwargs
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -401,18 +403,35 @@ class override_settings(TestContextDecorator):
|
|||||||
self.wrapped = settings._wrapped
|
self.wrapped = settings._wrapped
|
||||||
settings._wrapped = override
|
settings._wrapped = override
|
||||||
for key, new_value in self.options.items():
|
for key, new_value in self.options.items():
|
||||||
setting_changed.send(sender=settings._wrapped.__class__,
|
try:
|
||||||
setting=key, value=new_value, enter=True)
|
setting_changed.send(
|
||||||
|
sender=settings._wrapped.__class__,
|
||||||
|
setting=key, value=new_value, enter=True,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
self.enable_exception = exc
|
||||||
|
self.disable()
|
||||||
|
|
||||||
def disable(self):
|
def disable(self):
|
||||||
if 'INSTALLED_APPS' in self.options:
|
if 'INSTALLED_APPS' in self.options:
|
||||||
apps.unset_installed_apps()
|
apps.unset_installed_apps()
|
||||||
settings._wrapped = self.wrapped
|
settings._wrapped = self.wrapped
|
||||||
del self.wrapped
|
del self.wrapped
|
||||||
|
responses = []
|
||||||
for key in self.options:
|
for key in self.options:
|
||||||
new_value = getattr(settings, key, None)
|
new_value = getattr(settings, key, None)
|
||||||
setting_changed.send(sender=settings._wrapped.__class__,
|
responses_for_setting = setting_changed.send_robust(
|
||||||
setting=key, value=new_value, enter=False)
|
sender=settings._wrapped.__class__,
|
||||||
|
setting=key, value=new_value, enter=False,
|
||||||
|
)
|
||||||
|
responses.extend(responses_for_setting)
|
||||||
|
if self.enable_exception is not None:
|
||||||
|
exc = self.enable_exception
|
||||||
|
self.enable_exception = None
|
||||||
|
raise exc
|
||||||
|
for _, response in responses:
|
||||||
|
if isinstance(response, Exception):
|
||||||
|
raise response
|
||||||
|
|
||||||
def save_options(self, test_func):
|
def save_options(self, test_func):
|
||||||
if test_func._overridden_settings is None:
|
if test_func._overridden_settings is None:
|
||||||
|
@ -441,3 +441,106 @@ class TestListSettings(unittest.TestCase):
|
|||||||
finally:
|
finally:
|
||||||
del sys.modules['fake_settings_module']
|
del sys.modules['fake_settings_module']
|
||||||
delattr(settings_module, setting)
|
delattr(settings_module, setting)
|
||||||
|
|
||||||
|
|
||||||
|
class SettingChangeEnterException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SettingChangeExitException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OverrideSettingsIsolationOnExceptionTests(SimpleTestCase):
|
||||||
|
"""
|
||||||
|
The override_settings context manager restore settings if one of the
|
||||||
|
receivers of "setting_changed" signal fails. Check the three cases of
|
||||||
|
receiver failure detailed in receiver(). In each case, ALL receivers are
|
||||||
|
called when exiting the context manager.
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
signals.setting_changed.connect(self.receiver)
|
||||||
|
self.addCleanup(signals.setting_changed.disconnect, self.receiver)
|
||||||
|
# Create a spy that's connected to the `setting_changed` signal and
|
||||||
|
# executed AFTER `self.receiver`.
|
||||||
|
self.spy_receiver = mock.Mock()
|
||||||
|
signals.setting_changed.connect(self.spy_receiver)
|
||||||
|
self.addCleanup(signals.setting_changed.disconnect, self.spy_receiver)
|
||||||
|
|
||||||
|
def receiver(self, **kwargs):
|
||||||
|
"""
|
||||||
|
A receiver that fails while certain settings are being changed.
|
||||||
|
- SETTING_BOTH raises an error while receiving the signal
|
||||||
|
on both entering and exiting the context manager.
|
||||||
|
- SETTING_ENTER raises an error only on enter.
|
||||||
|
- SETTING_EXIT raises an error only on exit.
|
||||||
|
"""
|
||||||
|
setting = kwargs['setting']
|
||||||
|
enter = kwargs['enter']
|
||||||
|
if setting in ('SETTING_BOTH', 'SETTING_ENTER') and enter:
|
||||||
|
raise SettingChangeEnterException
|
||||||
|
if setting in ('SETTING_BOTH', 'SETTING_EXIT') and not enter:
|
||||||
|
raise SettingChangeExitException
|
||||||
|
|
||||||
|
def check_settings(self):
|
||||||
|
"""Assert that settings for these tests aren't present."""
|
||||||
|
self.assertFalse(hasattr(settings, 'SETTING_BOTH'))
|
||||||
|
self.assertFalse(hasattr(settings, 'SETTING_ENTER'))
|
||||||
|
self.assertFalse(hasattr(settings, 'SETTING_EXIT'))
|
||||||
|
self.assertFalse(hasattr(settings, 'SETTING_PASS'))
|
||||||
|
|
||||||
|
def check_spy_receiver_exit_calls(self, call_count):
|
||||||
|
"""
|
||||||
|
Assert that `self.spy_receiver` was called exactly `call_count` times
|
||||||
|
with the ``enter=False`` keyword argument.
|
||||||
|
"""
|
||||||
|
kwargs_with_exit = [
|
||||||
|
kwargs for args, kwargs in self.spy_receiver.call_args_list
|
||||||
|
if ('enter', False) in kwargs.items()
|
||||||
|
]
|
||||||
|
self.assertEqual(len(kwargs_with_exit), call_count)
|
||||||
|
|
||||||
|
def test_override_settings_both(self):
|
||||||
|
"""Receiver fails on both enter and exit."""
|
||||||
|
with self.assertRaises(SettingChangeEnterException):
|
||||||
|
with override_settings(SETTING_PASS='BOTH', SETTING_BOTH='BOTH'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.check_settings()
|
||||||
|
# Two settings were touched, so expect two calls of `spy_receiver`.
|
||||||
|
self.check_spy_receiver_exit_calls(call_count=2)
|
||||||
|
|
||||||
|
def test_override_settings_enter(self):
|
||||||
|
"""Receiver fails on enter only."""
|
||||||
|
with self.assertRaises(SettingChangeEnterException):
|
||||||
|
with override_settings(SETTING_PASS='ENTER', SETTING_ENTER='ENTER'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.check_settings()
|
||||||
|
# Two settings were touched, so expect two calls of `spy_receiver`.
|
||||||
|
self.check_spy_receiver_exit_calls(call_count=2)
|
||||||
|
|
||||||
|
def test_override_settings_exit(self):
|
||||||
|
"""Receiver fails on exit only."""
|
||||||
|
with self.assertRaises(SettingChangeExitException):
|
||||||
|
with override_settings(SETTING_PASS='EXIT', SETTING_EXIT='EXIT'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.check_settings()
|
||||||
|
# Two settings were touched, so expect two calls of `spy_receiver`.
|
||||||
|
self.check_spy_receiver_exit_calls(call_count=2)
|
||||||
|
|
||||||
|
def test_override_settings_reusable_on_enter(self):
|
||||||
|
"""
|
||||||
|
Error is raised correctly when reusing the same override_settings
|
||||||
|
instance.
|
||||||
|
"""
|
||||||
|
@override_settings(SETTING_ENTER='ENTER')
|
||||||
|
def decorated_function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
with self.assertRaises(SettingChangeEnterException):
|
||||||
|
decorated_function()
|
||||||
|
signals.setting_changed.disconnect(self.receiver)
|
||||||
|
# This call shouldn't raise any errors.
|
||||||
|
decorated_function()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user