Skip to content

Commit 751ad89

Browse files
committed
fix django 5.2 compatibility
Changes: - fix django 5.2 compatibility - add tests for ChannelsLiveServerTestCase - add test project and use its settings - fix hangup when the server failed to start
1 parent 8bf5c7d commit 751ad89

File tree

9 files changed

+200
-35
lines changed

9 files changed

+200
-35
lines changed

channels/testing/live.py

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,36 +39,44 @@ def live_server_url(self):
3939
def live_server_ws_url(self):
4040
return "ws://%s:%s" % (self.host, self._port)
4141

42-
def _pre_setup(self):
42+
@classmethod
43+
def setUpClass(cls):
4344
for connection in connections.all():
44-
if self._is_in_memory_db(connection):
45+
if cls._is_in_memory_db(connection):
4546
raise ImproperlyConfigured(
4647
"ChannelLiveServerTestCase can not be used with in memory databases"
4748
)
4849

49-
super(ChannelsLiveServerTestCase, self)._pre_setup()
50+
super().setUpClass()
5051

51-
self._live_server_modified_settings = modify_settings(
52-
ALLOWED_HOSTS={"append": self.host}
52+
cls._live_server_modified_settings = modify_settings(
53+
ALLOWED_HOSTS={"append": cls.host}
5354
)
54-
self._live_server_modified_settings.enable()
55+
cls._live_server_modified_settings.enable()
5556

5657
get_application = partial(
5758
make_application,
58-
static_wrapper=self.static_wrapper if self.serve_static else None,
59+
static_wrapper=cls.static_wrapper if cls.serve_static else None,
5960
)
60-
self._server_process = self.ProtocolServerProcess(self.host, get_application)
61-
self._server_process.start()
62-
self._server_process.ready.wait()
63-
self._port = self._server_process.port.value
61+
cls._server_process = cls.ProtocolServerProcess(cls.host, get_application)
62+
cls._server_process.start()
63+
while True:
64+
if not cls._server_process.ready.wait(timeout=1):
65+
if cls._server_process.is_alive():
66+
continue
67+
raise RuntimeError("Server stopped") from None
68+
break
69+
cls._port = cls._server_process.port.value
6470

65-
def _post_teardown(self):
66-
self._server_process.terminate()
67-
self._server_process.join()
68-
self._live_server_modified_settings.disable()
69-
super(ChannelsLiveServerTestCase, self)._post_teardown()
71+
@classmethod
72+
def tearDownClass(cls):
73+
cls._server_process.terminate()
74+
cls._server_process.join()
75+
cls._live_server_modified_settings.disable()
76+
super().tearDownClass()
7077

71-
def _is_in_memory_db(self, connection):
78+
@classmethod
79+
def _is_in_memory_db(cls, connection):
7280
"""
7381
Check if DatabaseWrapper holds in memory database.
7482
"""

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ tests =
4444
pytest
4545
pytest-django
4646
pytest-asyncio
47+
httpx
4748
daphne =
4849
daphne>=4.0.0
4950

tests/conftest.py

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
1+
import os
2+
13
import pytest
24
from django.conf import settings
35

46

57
def pytest_configure():
6-
settings.configure(
7-
DATABASES={
8-
"default": {
9-
"ENGINE": "django.db.backends.sqlite3",
10-
# Override Django’s default behaviour of using an in-memory database
11-
# in tests for SQLite, since that avoids connection.close() working.
12-
"TEST": {"NAME": "test_db.sqlite3"},
13-
}
14-
},
15-
INSTALLED_APPS=[
16-
"django.contrib.auth",
17-
"django.contrib.contenttypes",
18-
"django.contrib.sessions",
19-
"django.contrib.admin",
20-
"channels",
21-
],
22-
SECRET_KEY="Not_a_secret_key",
23-
)
8+
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.testproject.settings"
9+
settings._setup()
2410

2511

2612
def pytest_generate_tests(metafunc):

tests/test_live_testcase.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import httpx
2+
from django.conf import settings
3+
4+
from channels.testing import ChannelsLiveServerTestCase
5+
6+
7+
def test_settings():
8+
assert settings.SETTINGS_MODULE == "tests.testproject.settings"
9+
10+
11+
class TestLiveNoStatic(ChannelsLiveServerTestCase):
12+
serve_static = False
13+
14+
def test_properties(self):
15+
# test properties
16+
self.assertEqual(
17+
self.live_server_ws_url, self.live_server_url.replace("http", "ws", 1)
18+
)
19+
20+
def test_resolving(self):
21+
result = httpx.get(f"{self.live_server_url}/admin", follow_redirects=True)
22+
self.assertEqual(result.status_code, 200)
23+
result = httpx.get(f"{self.live_server_url}/addsdsmidsdsn")
24+
self.assertEqual(result.status_code, 404)
25+
26+
27+
class TestLiveWithStatic(ChannelsLiveServerTestCase):
28+
def test_properties(self):
29+
# test properties
30+
self.assertEqual(
31+
self.live_server_ws_url, self.live_server_url.replace("http", "ws", 1)
32+
)
33+
34+
def test_resolving(self):
35+
result = httpx.get(f"{self.live_server_url}/admin", follow_redirects=True)
36+
self.assertEqual(result.status_code, 200)
37+
result = httpx.get(f"{self.live_server_url}/addsdsmidsdsn")
38+
self.assertEqual(result.status_code, 404)

tests/testproject/__init__.py

Whitespace-only changes.

tests/testproject/asgi.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
ASGI config for testproject project.
3+
4+
It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.asgi import get_asgi_application
13+
14+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.testproject.settings")
15+
16+
application = get_asgi_application()

tests/testproject/settings.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
Django settings for testproject project.
3+
4+
Generated by 'django-admin startproject' using Django 5.2.3.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/5.2/topics/settings/
8+
9+
For the full list of settings and their values, see
10+
https://docs.djangoproject.com/en/5.2/ref/settings/
11+
"""
12+
13+
SECRET_KEY = "Not_a_secret_key"
14+
15+
# SECURITY WARNING: don't run with debug turned on in production!
16+
DEBUG = True
17+
18+
ALLOWED_HOSTS = []
19+
20+
21+
# Application definition
22+
23+
INSTALLED_APPS = [
24+
"django.contrib.admin",
25+
"django.contrib.auth",
26+
"django.contrib.contenttypes",
27+
"django.contrib.sessions",
28+
"django.contrib.messages",
29+
"django.contrib.staticfiles",
30+
"channels",
31+
]
32+
33+
MIDDLEWARE = [
34+
"django.middleware.security.SecurityMiddleware",
35+
"django.contrib.sessions.middleware.SessionMiddleware",
36+
"django.middleware.common.CommonMiddleware",
37+
"django.middleware.csrf.CsrfViewMiddleware",
38+
"django.contrib.auth.middleware.AuthenticationMiddleware",
39+
"django.contrib.messages.middleware.MessageMiddleware",
40+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
41+
]
42+
43+
ROOT_URLCONF = "tests.testproject.urls"
44+
45+
TEMPLATES = [
46+
{
47+
"BACKEND": "django.template.backends.django.DjangoTemplates",
48+
"DIRS": [],
49+
"APP_DIRS": True,
50+
"OPTIONS": {
51+
"context_processors": [
52+
"django.template.context_processors.request",
53+
"django.contrib.auth.context_processors.auth",
54+
"django.contrib.messages.context_processors.messages",
55+
],
56+
},
57+
},
58+
]
59+
60+
61+
# Database
62+
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
63+
64+
DATABASES = {
65+
"default": {
66+
"ENGINE": "django.db.backends.sqlite3",
67+
# Override Django’s default behaviour of using an in-memory database
68+
# in tests for SQLite, since that avoids connection.close() working.
69+
"TEST": {"NAME": "test_db.sqlite3"},
70+
}
71+
}
72+
73+
74+
STATIC_URL = "static/"
75+
76+
ASGI_APPLICATION = "tests.testproject.asgi.application"
77+
78+
WSGI_APPLICATION = "tests.testproject.wsgi.application"

tests/testproject/urls.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
URL configuration for testproject project.
3+
4+
The `urlpatterns` list routes URLs to views. For more information please see:
5+
https://docs.djangoproject.com/en/5.2/topics/http/urls/
6+
Examples:
7+
Function views
8+
1. Add an import: from my_app import views
9+
2. Add a URL to urlpatterns: path('', views.home, name='home')
10+
Class-based views
11+
1. Add an import: from other_app.views import Home
12+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13+
Including another URLconf
14+
1. Import the include() function: from django.urls import include, path
15+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16+
"""
17+
from django.contrib import admin
18+
from django.urls import path
19+
20+
urlpatterns = [
21+
path('admin/', admin.site.urls),
22+
]

tests/testproject/wsgi.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
WSGI config for testproject project.
3+
4+
It exposes the WSGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.wsgi import get_wsgi_application
13+
14+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.testproject.settings")
15+
16+
application = get_wsgi_application()

0 commit comments

Comments
 (0)