Skip to content

RecursionError when mixing use of override_settings and settings fixture #809

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
theY4Kman opened this issue Jan 20, 2020 · 5 comments
Open

Comments

@theY4Kman
Copy link
Contributor

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
@theY4Kman
Copy link
Contributor Author

In practice, I've worked around this by wrapping settings.finalize() with override_settings(), so the true Django settings is always restored

@pytest.fixture
def settings(settings):
    with override_settings():
        yield settings
        settings.finalize()

@benjaminrigaud
Copy link

Out of curiosity, can't you replace override_settings by the settings fixture?
Aren't they serving similar purposes?

@craigds
Copy link

craigds commented Jul 22, 2020

One valid reason not to use the settings fixture is because it's function scoped. So if you want to override settings in a class/module/session fixture you can't use it. You can use override_settings in that context

@graingert
Copy link
Member

One valid reason not to use the settings fixture is because it's function scoped. So if you want to override settings in a class/module/session fixture you can't use it. You can use override_settings in that context

pytest-dev/pytest#6888

looks like you could want a settings_session and settings_module and settings_class for each of the scopes

@theY4Kman
Copy link
Contributor Author

One valid reason not to use the settings fixture is because it's function scoped.

This is exactly why. We have a session-scoped fixture running a live test server thread, whose handler must be setup and torn down in a specific order for each participating test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants