Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import os
import pathlib
import sys
import threading
import types
from collections.abc import Generator
from contextlib import AbstractContextManager
Expand Down Expand Up @@ -64,6 +65,8 @@

# ############### pytest hooks ################

urlconf_lock = threading.Lock()


@pytest.hookimpl()
def pytest_addoption(parser: pytest.Parser) -> None:
Expand Down Expand Up @@ -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")
Expand Down
44 changes: 44 additions & 0 deletions tests/test_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,47 @@ 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this test over to my main branch, and ran this on py3.13

count=0
while true; do
    count=$((count + 1))
    if ! pytest tests/test_urls.py -vv > /dev/null 2>&1; then
        echo "Test failed on iteration $count"
        break
    fi
    printf "."
done

it has not failed -- keeps going, after 100+ iterations (keep in mind, i only copied the test, not the "fix")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PS. I also ran this with pytest -n auto (same loop as above) -- no difference, i dont see the race condition

"Test that the URL cache clearing is thread-safe with pytest-xdist."
pytest.importorskip("xdist")

django_pytester.makepyfile(
empty="urlpatterns = []",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used

Suggested change
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
Loading