From 049534b5da580b54d5e7e9a0dd45dd6749e47752 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind Date: Wed, 13 Aug 2025 23:08:52 +0530 Subject: [PATCH 1/3] Fix: Make URL cache clearing thread-safe in _django_set_urlconf --- pytest_django/plugin.py | 23 ++++++++++++---------- tests/test_urls.py | 43 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 9bab8971..959ceaf5 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -16,6 +16,7 @@ from contextlib import AbstractContextManager from functools import reduce from typing import TYPE_CHECKING, NoReturn +import threading import pytest @@ -64,6 +65,8 @@ # ############### pytest hooks ################ +urlconf_lock = threading.Lock() + @pytest.hookimpl() def pytest_addoption(parser: pytest.Parser) -> None: @@ -646,20 +649,20 @@ def _django_set_urlconf(request: pytest.FixtureRequest) -> Generator[None, None, import django.conf from django.urls import clear_url_caches, set_urlconf - urls = validate_urls(marker) - original_urlconf = django.conf.settings.ROOT_URLCONF - django.conf.settings.ROOT_URLCONF = urls - clear_url_caches() - set_urlconf(None) + with urlconf_lock: + urls = validate_urls(marker) + original_urlconf = django.conf.settings.ROOT_URLCONF + django.conf.settings.ROOT_URLCONF = urls + clear_url_caches() + set_urlconf(None) yield if marker: - django.conf.settings.ROOT_URLCONF = original_urlconf - # Copy the pattern from - # https://github.com/django/django/blob/main/django/test/signals.py#L152 - clear_url_caches() - set_urlconf(None) + with urlconf_lock: + django.conf.settings.ROOT_URLCONF = original_urlconf + clear_url_caches() + set_urlconf(None) @pytest.fixture(autouse=True, scope="session") diff --git a/tests/test_urls.py b/tests/test_urls.py index 78c55d5c..f404c294 100644 --- a/tests/test_urls.py +++ b/tests/test_urls.py @@ -107,3 +107,46 @@ def test_something_else(): result = django_pytester.runpytest_subprocess() assert result.ret == 0 + +@pytest.mark.django_project( + extra_settings=""" + ROOT_URLCONF = "empty" + """ +) +def test_urls_concurrent(django_pytester: DjangoPytester) -> None: + "Test that the URL cache clearing is thread-safe with pytest-xdist." + pytest.importorskip("xdist") + + django_pytester.makepyfile( + empty="urlpatterns = []", + urls1=""" + from django.urls import path + urlpatterns = [path('url1/', lambda r: None, name='url1')] + """, + urls2=""" + from django.urls import path + urlpatterns = [path('url2/', lambda r: None, name='url2')] + """, + ) + + django_pytester.create_test_module( + """ + import pytest + from django.urls import reverse, NoReverseMatch + + @pytest.mark.urls('urls1') + def test_urls1(): + reverse('url1') + with pytest.raises(NoReverseMatch): + reverse('url2') + + @pytest.mark.urls('urls2') + def test_urls2(): + reverse('url2') + with pytest.raises(NoReverseMatch): + reverse('url1') + """ + ) + + result = django_pytester.runpytest_subprocess("-n", "2") + assert result.ret == 0 From c746a9bb8fc2295bdaf55d90e09faed8f1009eec Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind Date: Wed, 13 Aug 2025 23:24:35 +0530 Subject: [PATCH 2/3] fix linting error --- pytest_django/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 959ceaf5..0df25b0e 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -11,12 +11,12 @@ import os import pathlib import sys +import threading import types from collections.abc import Generator from contextlib import AbstractContextManager from functools import reduce from typing import TYPE_CHECKING, NoReturn -import threading import pytest From c2b543868f7a2db8660cd1e1d4b184f1ae125d22 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind Date: Wed, 13 Aug 2025 23:27:54 +0530 Subject: [PATCH 3/3] fix linting error --- tests/test_urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_urls.py b/tests/test_urls.py index f404c294..847b3e43 100644 --- a/tests/test_urls.py +++ b/tests/test_urls.py @@ -108,6 +108,7 @@ def test_something_else(): result = django_pytester.runpytest_subprocess() assert result.ret == 0 + @pytest.mark.django_project( extra_settings=""" ROOT_URLCONF = "empty"