pytest-dev / pytest-dev/pytest-django
RecursionError when mixing use of override_settings and settings fixture
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.5k
- Forks
- 367
- PR merge metrics
- No merged PRs in 30d
Description
Because the settings fixture undoes its modifications once at fixture teardown, if override_settings is used after settings fixture setup, but before any settings.VAL = 1 setattr statements, the settings fixture will end up restoring django.conf.settings._wrapped to the fake settings installed by override_settings.
After a critical amount of tests where this occurs, Python will raise a RecursionError on any attribute access to django.conf.settings.
Here's a minimal example to show what's going on:
import pytest
from django.conf import settings
from django.test import override_settings
@pytest.fixture
def overrides():
with override_settings(OVERRIDE=1):
yield
@pytest.fixture
def add_settings(settings):
settings.ADDED = 1
def get_settings_override_depth():
depth = 0
root = settings._wrapped
while hasattr(root, 'default_settings'):
depth += 1
root = root.default_settings
return depth
@pytest.mark.parametrize('i', range(2))
def test_settings_fixture_used_first(add_settings, overrides, i):
# This will pass, as the settings fixture will restore the true Django settings
assert get_settings_override_depth() == 2
@pytest.mark.parametrize('i', range(2))
def test_settings_fixture_used_after_override_settings(overrides, add_settings, i):
# This will pass, as the override_settings will teardown last,
# restoring the true Django settings
assert get_settings_override_depth() == 2
@pytest.mark.parametrize('i', range(2))
def test_settings_fixture_teardown_called_after_override_settings_teardown(settings, overrides, add_settings, i):
# This will fail on the second test, because the settings fixture will teardown
# after override_settings, but the restored Django settings will have come
# from override_settings
assert get_settings_override_depth() == 2
And a test to surface the RecursionError:
# Number of frames expected before pytest hands off execution to the test function,
# as well as the number of frames in between getattr(settings, ...) and the eventual
# getattr(settings._wrapped, ...)
BASE_FRAME_DEPTH = 51
# The number of frames an additional override_settings() adds to settings accesses
SETTINGS_DEPTH_MULTIPLIER = 2
# Number of stack frames before Python raises a RecursionError
RECURSION_LIMIT = __import__('sys').getrecursionlimit()
NUM_REPETITIONS_TO_RECURSIONERROR = (
(RECURSION_LIMIT - BASE_FRAME_DEPTH) // SETTINGS_DEPTH_MULTIPLIER
)
@pytest.mark.parametrize('i', range(NUM_REPETITIONS_TO_RECURSIONERROR))
def test_show_recursion_error(settings, overrides, add_settings, i):
# This will fail on the last test
pass
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the minimal pytest examples in the issue, especially test_settings_fixture_teardown_called_after_override_settings_teardown and test_show_recursion_error. Trace the settings fixture teardown alongside override_settings teardown and verify that repeated parametrized runs do not increase the override depth or eventually raise RecursionError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100