123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- """
- Tests for django.core.servers.
- """
- import errno
- import os
- import socket
- import threading
- from http.client import HTTPConnection
- from urllib.error import HTTPError
- from urllib.parse import urlencode
- from urllib.request import urlopen
- from django.conf import settings
- from django.core.servers.basehttp import ThreadedWSGIServer, WSGIServer
- from django.db import DEFAULT_DB_ALIAS, connections
- from django.test import LiveServerTestCase, override_settings
- from django.test.testcases import LiveServerThread, QuietWSGIRequestHandler
- from .models import Person
- TEST_ROOT = os.path.dirname(__file__)
- TEST_SETTINGS = {
- "MEDIA_URL": "media/",
- "MEDIA_ROOT": os.path.join(TEST_ROOT, "media"),
- "STATIC_URL": "static/",
- "STATIC_ROOT": os.path.join(TEST_ROOT, "static"),
- }
- @override_settings(ROOT_URLCONF="servers.urls", **TEST_SETTINGS)
- class LiveServerBase(LiveServerTestCase):
- available_apps = [
- "servers",
- "django.contrib.auth",
- "django.contrib.contenttypes",
- "django.contrib.sessions",
- ]
- fixtures = ["testdata.json"]
- def urlopen(self, url):
- return urlopen(self.live_server_url + url)
- class CloseConnectionTestServer(ThreadedWSGIServer):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- # This event is set right after the first time a request closes its
- # database connections.
- self._connections_closed = threading.Event()
- def _close_connections(self):
- super()._close_connections()
- self._connections_closed.set()
- class CloseConnectionTestLiveServerThread(LiveServerThread):
- server_class = CloseConnectionTestServer
- def _create_server(self, connections_override=None):
- return super()._create_server(connections_override=self.connections_override)
- class LiveServerTestCloseConnectionTest(LiveServerBase):
- server_thread_class = CloseConnectionTestLiveServerThread
- @classmethod
- def _make_connections_override(cls):
- conn = connections[DEFAULT_DB_ALIAS]
- cls.conn = conn
- cls.old_conn_max_age = conn.settings_dict["CONN_MAX_AGE"]
- # Set the connection's CONN_MAX_AGE to None to simulate the
- # CONN_MAX_AGE setting being set to None on the server. This prevents
- # Django from closing the connection and allows testing that
- # ThreadedWSGIServer closes connections.
- conn.settings_dict["CONN_MAX_AGE"] = None
- # Pass a database connection through to the server to check it is being
- # closed by ThreadedWSGIServer.
- return {DEFAULT_DB_ALIAS: conn}
- @classmethod
- def tearDownConnectionTest(cls):
- cls.conn.settings_dict["CONN_MAX_AGE"] = cls.old_conn_max_age
- @classmethod
- def tearDownClass(cls):
- cls.tearDownConnectionTest()
- super().tearDownClass()
- def test_closes_connections(self):
- # The server's request thread sets this event after closing
- # its database connections.
- closed_event = self.server_thread.httpd._connections_closed
- conn = self.conn
- # Open a connection to the database.
- conn.connect()
- self.assertIsNotNone(conn.connection)
- with self.urlopen("/model_view/") as f:
- # The server can access the database.
- self.assertEqual(f.read().splitlines(), [b"jane", b"robert"])
- # Wait for the server's request thread to close the connection.
- # A timeout of 0.1 seconds should be more than enough. If the wait
- # times out, the assertion after should fail.
- closed_event.wait(timeout=0.1)
- self.assertIsNone(conn.connection)
- class FailingLiveServerThread(LiveServerThread):
- def _create_server(self):
- raise RuntimeError("Error creating server.")
- class LiveServerTestCaseSetupTest(LiveServerBase):
- server_thread_class = FailingLiveServerThread
- @classmethod
- def check_allowed_hosts(cls, expected):
- if settings.ALLOWED_HOSTS != expected:
- raise RuntimeError(f"{settings.ALLOWED_HOSTS} != {expected}")
- @classmethod
- def setUpClass(cls):
- cls.check_allowed_hosts(["testserver"])
- try:
- super().setUpClass()
- except RuntimeError:
- # LiveServerTestCase's change to ALLOWED_HOSTS should be reverted.
- cls.doClassCleanups()
- cls.check_allowed_hosts(["testserver"])
- else:
- raise RuntimeError("Server did not fail.")
- cls.set_up_called = True
- def test_set_up_class(self):
- self.assertIs(self.set_up_called, True)
- class LiveServerAddress(LiveServerBase):
- @classmethod
- def setUpClass(cls):
- super().setUpClass()
- # put it in a list to prevent descriptor lookups in test
- cls.live_server_url_test = [cls.live_server_url]
- def test_live_server_url_is_class_property(self):
- self.assertIsInstance(self.live_server_url_test[0], str)
- self.assertEqual(self.live_server_url_test[0], self.live_server_url)
- class LiveServerSingleThread(LiveServerThread):
- def _create_server(self):
- return WSGIServer(
- (self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False
- )
- class SingleThreadLiveServerTestCase(LiveServerTestCase):
- server_thread_class = LiveServerSingleThread
- class LiveServerViews(LiveServerBase):
- def test_protocol(self):
- """Launched server serves with HTTP 1.1."""
- with self.urlopen("/example_view/") as f:
- self.assertEqual(f.version, 11)
- def test_closes_connection_without_content_length(self):
- """
- An HTTP 1.1 server is supposed to support keep-alive. Since our
- development server is rather simple we support it only in cases where
- we can detect a content length from the response. This should be doable
- for all simple views and streaming responses where an iterable with
- length of one is passed. The latter follows as result of `set_content_length`
- from https://github.com/python/cpython/blob/main/Lib/wsgiref/handlers.py.
- If we cannot detect a content length we explicitly set the `Connection`
- header to `close` to notify the client that we do not actually support
- it.
- """
- conn = HTTPConnection(
- LiveServerViews.server_thread.host,
- LiveServerViews.server_thread.port,
- timeout=1,
- )
- try:
- conn.request(
- "GET", "/streaming_example_view/", headers={"Connection": "keep-alive"}
- )
- response = conn.getresponse()
- self.assertTrue(response.will_close)
- self.assertEqual(response.read(), b"Iamastream")
- self.assertEqual(response.status, 200)
- self.assertEqual(response.getheader("Connection"), "close")
- conn.request(
- "GET", "/streaming_example_view/", headers={"Connection": "close"}
- )
- response = conn.getresponse()
- self.assertTrue(response.will_close)
- self.assertEqual(response.read(), b"Iamastream")
- self.assertEqual(response.status, 200)
- self.assertEqual(response.getheader("Connection"), "close")
- finally:
- conn.close()
- def test_keep_alive_on_connection_with_content_length(self):
- """
- See `test_closes_connection_without_content_length` for details. This
- is a follow up test, which ensure that we do not close the connection
- if not needed, hence allowing us to take advantage of keep-alive.
- """
- conn = HTTPConnection(
- LiveServerViews.server_thread.host, LiveServerViews.server_thread.port
- )
- try:
- conn.request("GET", "/example_view/", headers={"Connection": "keep-alive"})
- response = conn.getresponse()
- self.assertFalse(response.will_close)
- self.assertEqual(response.read(), b"example view")
- self.assertEqual(response.status, 200)
- self.assertIsNone(response.getheader("Connection"))
- conn.request("GET", "/example_view/", headers={"Connection": "close"})
- response = conn.getresponse()
- self.assertFalse(response.will_close)
- self.assertEqual(response.read(), b"example view")
- self.assertEqual(response.status, 200)
- self.assertIsNone(response.getheader("Connection"))
- finally:
- conn.close()
- def test_keep_alive_connection_clears_previous_request_data(self):
- conn = HTTPConnection(
- LiveServerViews.server_thread.host, LiveServerViews.server_thread.port
- )
- try:
- conn.request(
- "POST", "/method_view/", b"{}", headers={"Connection": "keep-alive"}
- )
- response = conn.getresponse()
- self.assertFalse(response.will_close)
- self.assertEqual(response.status, 200)
- self.assertEqual(response.read(), b"POST")
- conn.request(
- "POST", "/method_view/", b"{}", headers={"Connection": "close"}
- )
- response = conn.getresponse()
- self.assertFalse(response.will_close)
- self.assertEqual(response.status, 200)
- self.assertEqual(response.read(), b"POST")
- finally:
- conn.close()
- def test_404(self):
- with self.assertRaises(HTTPError) as err:
- self.urlopen("/")
- err.exception.close()
- self.assertEqual(err.exception.code, 404, "Expected 404 response")
- def test_view(self):
- with self.urlopen("/example_view/") as f:
- self.assertEqual(f.read(), b"example view")
- def test_static_files(self):
- with self.urlopen("/static/example_static_file.txt") as f:
- self.assertEqual(f.read().rstrip(b"\r\n"), b"example static file")
- def test_no_collectstatic_emulation(self):
- """
- LiveServerTestCase reports a 404 status code when HTTP client
- tries to access a static file that isn't explicitly put under
- STATIC_ROOT.
- """
- with self.assertRaises(HTTPError) as err:
- self.urlopen("/static/another_app/another_app_static_file.txt")
- err.exception.close()
- self.assertEqual(err.exception.code, 404, "Expected 404 response")
- def test_media_files(self):
- with self.urlopen("/media/example_media_file.txt") as f:
- self.assertEqual(f.read().rstrip(b"\r\n"), b"example media file")
- def test_environ(self):
- with self.urlopen("/environ_view/?%s" % urlencode({"q": "тест"})) as f:
- self.assertIn(b"QUERY_STRING: 'q=%D1%82%D0%B5%D1%81%D1%82'", f.read())
- @override_settings(ROOT_URLCONF="servers.urls")
- class SingleThreadLiveServerViews(SingleThreadLiveServerTestCase):
- available_apps = ["servers"]
- def test_closes_connection_with_content_length(self):
- """
- Contrast to
- LiveServerViews.test_keep_alive_on_connection_with_content_length().
- Persistent connections require threading server.
- """
- conn = HTTPConnection(
- SingleThreadLiveServerViews.server_thread.host,
- SingleThreadLiveServerViews.server_thread.port,
- timeout=1,
- )
- try:
- conn.request("GET", "/example_view/", headers={"Connection": "keep-alive"})
- response = conn.getresponse()
- self.assertTrue(response.will_close)
- self.assertEqual(response.read(), b"example view")
- self.assertEqual(response.status, 200)
- self.assertEqual(response.getheader("Connection"), "close")
- finally:
- conn.close()
- class LiveServerDatabase(LiveServerBase):
- def test_fixtures_loaded(self):
- """
- Fixtures are properly loaded and visible to the live server thread.
- """
- with self.urlopen("/model_view/") as f:
- self.assertEqual(f.read().splitlines(), [b"jane", b"robert"])
- def test_database_writes(self):
- """
- Data written to the database by a view can be read.
- """
- with self.urlopen("/create_model_instance/"):
- pass
- self.assertQuerysetEqual(
- Person.objects.order_by("pk"),
- ["jane", "robert", "emily"],
- lambda b: b.name,
- )
- class LiveServerPort(LiveServerBase):
- def test_port_bind(self):
- """
- Each LiveServerTestCase binds to a unique port or fails to start a
- server thread when run concurrently (#26011).
- """
- TestCase = type("TestCase", (LiveServerBase,), {})
- try:
- TestCase._start_server_thread()
- except OSError as e:
- if e.errno == errno.EADDRINUSE:
- # We're out of ports, LiveServerTestCase correctly fails with
- # an OSError.
- return
- # Unexpected error.
- raise
- self.assertNotEqual(
- self.live_server_url,
- TestCase.live_server_url,
- f"Acquired duplicate server addresses for server threads: "
- f"{self.live_server_url}",
- )
- def test_specified_port_bind(self):
- """LiveServerTestCase.port customizes the server's port."""
- TestCase = type("TestCase", (LiveServerBase,), {})
- # Find an open port and tell TestCase to use it.
- s = socket.socket()
- s.bind(("", 0))
- TestCase.port = s.getsockname()[1]
- s.close()
- TestCase._start_server_thread()
- self.assertEqual(
- TestCase.port,
- TestCase.server_thread.port,
- f"Did not use specified port for LiveServerTestCase thread: "
- f"{TestCase.port}",
- )
- class LiveServerThreadedTests(LiveServerBase):
- """If LiveServerTestCase isn't threaded, these tests will hang."""
- def test_view_calls_subview(self):
- url = "/subview_calling_view/?%s" % urlencode({"url": self.live_server_url})
- with self.urlopen(url) as f:
- self.assertEqual(f.read(), b"subview calling view: subview")
- def test_check_model_instance_from_subview(self):
- url = "/check_model_instance_from_subview/?%s" % urlencode(
- {
- "url": self.live_server_url,
- }
- )
- with self.urlopen(url) as f:
- self.assertIn(b"emily", f.read())
|