Skip to content

Commit 049534b

Browse files
committed
Fix: Make URL cache clearing thread-safe in _django_set_urlconf
1 parent 27e4f43 commit 049534b

2 files changed

Lines changed: 56 additions & 10 deletions

File tree

pytest_django/plugin.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from contextlib import AbstractContextManager
1717
from functools import reduce
1818
from typing import TYPE_CHECKING, NoReturn
19+
import threading
1920

2021
import pytest
2122

@@ -64,6 +65,8 @@
6465

6566
# ############### pytest hooks ################
6667

68+
urlconf_lock = threading.Lock()
69+
6770

6871
@pytest.hookimpl()
6972
def pytest_addoption(parser: pytest.Parser) -> None:
@@ -646,20 +649,20 @@ def _django_set_urlconf(request: pytest.FixtureRequest) -> Generator[None, None,
646649
import django.conf
647650
from django.urls import clear_url_caches, set_urlconf
648651

649-
urls = validate_urls(marker)
650-
original_urlconf = django.conf.settings.ROOT_URLCONF
651-
django.conf.settings.ROOT_URLCONF = urls
652-
clear_url_caches()
653-
set_urlconf(None)
652+
with urlconf_lock:
653+
urls = validate_urls(marker)
654+
original_urlconf = django.conf.settings.ROOT_URLCONF
655+
django.conf.settings.ROOT_URLCONF = urls
656+
clear_url_caches()
657+
set_urlconf(None)
654658

655659
yield
656660

657661
if marker:
658-
django.conf.settings.ROOT_URLCONF = original_urlconf
659-
# Copy the pattern from
660-
# https://github.com/django/django/blob/main/django/test/signals.py#L152
661-
clear_url_caches()
662-
set_urlconf(None)
662+
with urlconf_lock:
663+
django.conf.settings.ROOT_URLCONF = original_urlconf
664+
clear_url_caches()
665+
set_urlconf(None)
663666

664667

665668
@pytest.fixture(autouse=True, scope="session")

tests/test_urls.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,46 @@ def test_something_else():
107107

108108
result = django_pytester.runpytest_subprocess()
109109
assert result.ret == 0
110+
111+
@pytest.mark.django_project(
112+
extra_settings="""
113+
ROOT_URLCONF = "empty"
114+
"""
115+
)
116+
def test_urls_concurrent(django_pytester: DjangoPytester) -> None:
117+
"Test that the URL cache clearing is thread-safe with pytest-xdist."
118+
pytest.importorskip("xdist")
119+
120+
django_pytester.makepyfile(
121+
empty="urlpatterns = []",
122+
urls1="""
123+
from django.urls import path
124+
urlpatterns = [path('url1/', lambda r: None, name='url1')]
125+
""",
126+
urls2="""
127+
from django.urls import path
128+
urlpatterns = [path('url2/', lambda r: None, name='url2')]
129+
""",
130+
)
131+
132+
django_pytester.create_test_module(
133+
"""
134+
import pytest
135+
from django.urls import reverse, NoReverseMatch
136+
137+
@pytest.mark.urls('urls1')
138+
def test_urls1():
139+
reverse('url1')
140+
with pytest.raises(NoReverseMatch):
141+
reverse('url2')
142+
143+
@pytest.mark.urls('urls2')
144+
def test_urls2():
145+
reverse('url2')
146+
with pytest.raises(NoReverseMatch):
147+
reverse('url1')
148+
"""
149+
)
150+
151+
result = django_pytester.runpytest_subprocess("-n", "2")
152+
assert result.ret == 0

0 commit comments

Comments
 (0)