How Django's "override_settings" decorator works when launching tests in parallel

Viewed 409

I'm checking how Django's settings module is built and how the override_settings decorator deals with the settings when testing and I just can't see how the implementation of this decorator avoid problems when running the tests in parallel.

I see that it in the enable method it assigns to the settings' _wrapped attribute the settings values with the changes applied and that it stores a copy of the previous values that is then restored in the disable method. This works OK with me when executing it secuentially. But when running tests in parallel I can't see how this works without affecting other tests that also use the decorator, let's say to overwrite the same value. What I see is that the value set by the latest executed test will be returned everywhere when accessing settings.OVERRIDDEN_SETTING. In fact, this settings overriding should also affect the values returned in other tests even if they are not decorated.

I mean, if we have these two tests:

@override_settings(SETTING=1):
def test_1(self):
    ...
    ...
    print(settings.SETTING)

@override_settings(SETTING=2):
def test_2(self):
    ...
    ...
    print(settings.SETTING)

def test_3(self):
    ...
    ...
    print(settings.SETTING)

If they are run in parallel, and let's say test_1 is executed, starts executing it's code and in the meanwhile test_2 is called before the print statement in test_1 has been executed, by checking the decorator implementation, I would expect both of them to print 2 as the result of their print. And depending on when it gets executed, test_3 would return the original value, 1, or 2 if it's also run in parallel.

There must be something that I'm not taking into account because I don't think that this code is prone to this race condition after so much time being there.

Any help to understand this would be appreciated.

1 Answers

Parallel tests are run in separate processes, which each access its own copy of settings.

Therefore, Django's override_settings does not need to handle parallel tests specifically.

We can empirically verify that with unsafe direct modification instead of override_settings (note the sleep to ensure that test_3 runs after test_1 and test_2 modify the value):

from time import sleep

from django.conf import settings
from django.test import TestCase


class TestOverrideSettings1(TestCase):

    # @override_settings(SETTING=1)
    def test_1(self):
        settings.SETTING = 1
        print(settings.SETTING)


class TestOverrideSettings2(TestCase):

    # @override_settings(SETTING=2)
    def test_2(self):
        settings.SETTING = 2
        print(settings.SETTING)


class TestOverrideSettings3(TestCase):

    def test_3(self):
        sleep(1)
        print(settings.SETTING)

Running tests:

$ python manage.py test
1
.2
.2
.

Running tests in parallel:

$ python manage.py test --parallel
1
2
..0
.
Related