mirror of https://github.com/django/django.git
Fixed #26791 -- Replaced LiveServerTestCase port ranges with binding to port 0.
This commit is contained in:
parent
b5a1c3a6f5
commit
81cdcb66bc
|
@ -1,4 +1,3 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -46,12 +45,6 @@ class Command(BaseCommand):
|
|||
help='Tells Django to use specified test runner class instead of '
|
||||
'the one specified by the TEST_RUNNER setting.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--liveserver', action='store', dest='liveserver', default=None,
|
||||
help='Overrides the default address where the live server (used '
|
||||
'with LiveServerTestCase) is expected to run from. The '
|
||||
'default value is localhost:8081-8179.',
|
||||
)
|
||||
|
||||
test_runner_class = get_runner(settings, self.test_runner)
|
||||
|
||||
|
@ -64,10 +57,6 @@ class Command(BaseCommand):
|
|||
|
||||
TestRunner = get_runner(settings, options['testrunner'])
|
||||
|
||||
if options['liveserver'] is not None:
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options['liveserver']
|
||||
del options['liveserver']
|
||||
|
||||
test_runner = TestRunner(**options)
|
||||
failures = test_runner.run_tests(test_labels)
|
||||
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import difflib
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
import posixpath
|
||||
import socket
|
||||
import sys
|
||||
import threading
|
||||
import unittest
|
||||
|
@ -19,7 +16,7 @@ from unittest.util import safe_repr
|
|||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.core import mail
|
||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files import locks
|
||||
from django.core.handlers.wsgi import WSGIHandler, get_path_info
|
||||
from django.core.management import call_command
|
||||
|
@ -1235,10 +1232,9 @@ class LiveServerThread(threading.Thread):
|
|||
Thread for running a live http server while the tests are running.
|
||||
"""
|
||||
|
||||
def __init__(self, host, possible_ports, static_handler, connections_override=None):
|
||||
def __init__(self, host, static_handler, connections_override=None):
|
||||
self.host = host
|
||||
self.port = None
|
||||
self.possible_ports = possible_ports
|
||||
self.is_ready = threading.Event()
|
||||
self.error = None
|
||||
self.static_handler = static_handler
|
||||
|
@ -1258,28 +1254,8 @@ class LiveServerThread(threading.Thread):
|
|||
try:
|
||||
# Create the handler for serving static and media files
|
||||
handler = self.static_handler(_MediaFilesHandler(WSGIHandler()))
|
||||
|
||||
# Go through the list of possible ports, hoping that we can find
|
||||
# one that is free to use for the WSGI server.
|
||||
for index, port in enumerate(self.possible_ports):
|
||||
try:
|
||||
self.httpd = self._create_server(port)
|
||||
except socket.error as e:
|
||||
if (index + 1 < len(self.possible_ports) and
|
||||
e.errno == errno.EADDRINUSE):
|
||||
# This port is already in use, so we go on and try with
|
||||
# the next one in the list.
|
||||
continue
|
||||
else:
|
||||
# Either none of the given ports are free or the error
|
||||
# is something else than "Address already in use". So
|
||||
# we let that error bubble up to the main thread.
|
||||
raise
|
||||
else:
|
||||
# A free port was found.
|
||||
self.port = port
|
||||
break
|
||||
|
||||
self.httpd = self._create_server(0)
|
||||
self.port = self.httpd.server_address[1]
|
||||
self.httpd.set_app(handler)
|
||||
self.is_ready.set()
|
||||
self.httpd.serve_forever()
|
||||
|
@ -1308,13 +1284,12 @@ class LiveServerTestCase(TransactionTestCase):
|
|||
sqlite) and each thread needs to commit all their transactions so that the
|
||||
other thread can see the changes.
|
||||
"""
|
||||
|
||||
host = 'localhost'
|
||||
static_handler = _StaticFilesHandler
|
||||
|
||||
@classproperty
|
||||
def live_server_url(cls):
|
||||
return 'http://%s:%s' % (
|
||||
cls.server_thread.host, cls.server_thread.port)
|
||||
return 'http://%s:%s' % (cls.host, cls.server_thread.port)
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
@ -1328,35 +1303,11 @@ class LiveServerTestCase(TransactionTestCase):
|
|||
conn.allow_thread_sharing = True
|
||||
connections_override[conn.alias] = conn
|
||||
|
||||
specified_address = os.environ.get(
|
||||
'DJANGO_LIVE_TEST_SERVER_ADDRESS', 'localhost:8081-8179')
|
||||
cls._live_server_modified_settings = modify_settings(
|
||||
ALLOWED_HOSTS={'append': specified_address.split(':')[0]},
|
||||
ALLOWED_HOSTS={'append': cls.host},
|
||||
)
|
||||
cls._live_server_modified_settings.enable()
|
||||
|
||||
# The specified ports may be of the form '8000-8010,8080,9200-9300'
|
||||
# i.e. a comma-separated list of ports or ranges of ports, so we break
|
||||
# it down into a detailed list of all possible ports.
|
||||
possible_ports = []
|
||||
try:
|
||||
host, port_ranges = specified_address.split(':')
|
||||
for port_range in port_ranges.split(','):
|
||||
# A port range can be of either form: '8000' or '8000-8010'.
|
||||
extremes = list(map(int, port_range.split('-')))
|
||||
assert len(extremes) in [1, 2]
|
||||
if len(extremes) == 1:
|
||||
# Port range of the form '8000'
|
||||
possible_ports.append(extremes[0])
|
||||
else:
|
||||
# Port range of the form '8000-8010'
|
||||
for port in range(extremes[0], extremes[1] + 1):
|
||||
possible_ports.append(port)
|
||||
except Exception:
|
||||
msg = 'Invalid address ("%s") for live server.' % specified_address
|
||||
six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg), sys.exc_info()[2])
|
||||
# Launch the live server's thread
|
||||
cls.server_thread = cls._create_server_thread(host, possible_ports, connections_override)
|
||||
cls.server_thread = cls._create_server_thread(connections_override)
|
||||
cls.server_thread.daemon = True
|
||||
cls.server_thread.start()
|
||||
|
||||
|
@ -1369,10 +1320,9 @@ class LiveServerTestCase(TransactionTestCase):
|
|||
raise cls.server_thread.error
|
||||
|
||||
@classmethod
|
||||
def _create_server_thread(cls, host, possible_ports, connections_override):
|
||||
def _create_server_thread(cls, connections_override):
|
||||
return LiveServerThread(
|
||||
host,
|
||||
possible_ports,
|
||||
cls.host,
|
||||
cls.static_handler,
|
||||
connections_override=connections_override,
|
||||
)
|
||||
|
|
|
@ -1227,12 +1227,6 @@ Stops running tests and reports the failure immediately after a test fails.
|
|||
Controls the test runner class that is used to execute tests. This value
|
||||
overrides the value provided by the :setting:`TEST_RUNNER` setting.
|
||||
|
||||
.. django-admin-option:: --liveserver LIVESERVER
|
||||
|
||||
Overrides the default address where the live server (used with
|
||||
:class:`~django.test.LiveServerTestCase`) is expected to run from. The default
|
||||
value is ``localhost:8081-8179``.
|
||||
|
||||
.. django-admin-option:: --noinput, --no-input
|
||||
|
||||
Suppresses all user prompts. A typical prompt is a warning about deleting an
|
||||
|
|
|
@ -250,6 +250,15 @@ Django 1.11 sets PostgreSQL 9.3 as the minimum version it officially supports.
|
|||
Support for PostGIS 2.0 is also removed as PostgreSQL 9.2 is the last version
|
||||
to support it.
|
||||
|
||||
``LiveServerTestCase`` binds to port zero
|
||||
-----------------------------------------
|
||||
|
||||
Rather than taking a port range and iterating to find a free port,
|
||||
``LiveServerTestCase`` binds to port zero and relies on the operating system
|
||||
to assign a free port. The ``DJANGO_LIVE_TEST_SERVER_ADDRESS`` environment
|
||||
variable is no longer used, and as it's also no longer used, the
|
||||
``manage.py test --liveserver`` option is removed.
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
|
|
|
@ -814,39 +814,16 @@ This allows the use of automated test clients other than the
|
|||
client, to execute a series of functional tests inside a browser and simulate a
|
||||
real user's actions.
|
||||
|
||||
By default the live server listens on ``localhost`` and picks the first
|
||||
available port in the ``8081-8179`` range. Its full URL can be accessed with
|
||||
The live server listens on ``localhost`` and binds to port 0 which uses a free
|
||||
port assigned by the operating system. The server's URL can be accessed with
|
||||
``self.live_server_url`` during the tests.
|
||||
|
||||
If you'd like to select another address, you may pass a different one using the
|
||||
:option:`test --liveserver` option, for example:
|
||||
.. versionchanged:: 1.11
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ./manage.py test --liveserver=localhost:8082
|
||||
|
||||
Another way of changing the default server address is by setting the
|
||||
`DJANGO_LIVE_TEST_SERVER_ADDRESS` environment variable somewhere in your
|
||||
code (for example, in a :ref:`custom test runner<topics-testing-test_runner>`)::
|
||||
|
||||
import os
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = 'localhost:8082'
|
||||
|
||||
In the case where the tests are run by multiple processes in parallel (for
|
||||
example, in the context of several simultaneous `continuous integration`_
|
||||
builds), the processes will compete for the same address, and therefore your
|
||||
tests might randomly fail with an "Address already in use" error. To avoid this
|
||||
problem, you can pass a comma-separated list of ports or ranges of ports (at
|
||||
least as many as the number of potential parallel processes). For example:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ ./manage.py test --liveserver=localhost:8082,8090-8100,9000-9200,7041
|
||||
|
||||
Then, during test execution, each new live test server will try every specified
|
||||
port until it finds one that is free and takes it.
|
||||
|
||||
.. _continuous integration: https://en.wikipedia.org/wiki/Continuous_integration
|
||||
In older versions, Django tried a predefined port range which could be
|
||||
customized in various ways including the ``DJANGO_LIVE_TEST_SERVER_ADDRESS``
|
||||
environment variable. This is removed in favor of the simpler "bind to port
|
||||
0" technique.
|
||||
|
||||
To demonstrate how to use ``LiveServerTestCase``, let's write a simple Selenium
|
||||
test. First of all, you need to install the `selenium package`_ into your
|
||||
|
|
|
@ -28,7 +28,6 @@ from django.db.migrations.recorder import MigrationRecorder
|
|||
from django.test import (
|
||||
LiveServerTestCase, SimpleTestCase, TestCase, mock, override_settings,
|
||||
)
|
||||
from django.test.runner import DiscoverRunner
|
||||
from django.utils._os import npath, upath
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.six import PY2, PY3, StringIO
|
||||
|
@ -1276,46 +1275,6 @@ class ManageCheck(AdminScriptTestCase):
|
|||
self.assertNoOutput(out)
|
||||
|
||||
|
||||
class CustomTestRunner(DiscoverRunner):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
assert 'liveserver' not in kwargs
|
||||
super(CustomTestRunner, self).__init__(*args, **kwargs)
|
||||
|
||||
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class ManageTestCommand(AdminScriptTestCase):
|
||||
def test_liveserver(self):
|
||||
"""
|
||||
Ensure that the --liveserver option sets the environment variable
|
||||
correctly.
|
||||
Refs #2879.
|
||||
"""
|
||||
|
||||
# Backup original state
|
||||
address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
|
||||
old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
|
||||
|
||||
call_command('test', verbosity=0, testrunner='admin_scripts.tests.CustomTestRunner')
|
||||
|
||||
# Original state hasn't changed
|
||||
self.assertEqual('DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ, address_predefined)
|
||||
self.assertEqual(os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS'), old_address)
|
||||
|
||||
call_command('test', verbosity=0, testrunner='admin_scripts.tests.CustomTestRunner', liveserver='blah')
|
||||
|
||||
# Variable was correctly set
|
||||
self.assertEqual(os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'], 'blah')
|
||||
|
||||
# Restore original state
|
||||
if address_predefined:
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
|
||||
else:
|
||||
del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
|
||||
|
||||
|
||||
class ManageRunserver(AdminScriptTestCase):
|
||||
def setUp(self):
|
||||
from django.core.management.commands.runserver import Command
|
||||
|
|
|
@ -417,12 +417,6 @@ if __name__ == "__main__":
|
|||
help='Sort test suites and test cases in opposite order to debug '
|
||||
'test side effects not apparent with normal execution lineup.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--liveserver',
|
||||
help='Overrides the default address where the live server (used with '
|
||||
'LiveServerTestCase) is expected to run from. The default value '
|
||||
'is localhost:8081-8179.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--selenium', dest='selenium', action=ActionSelenium, metavar='BROWSERS',
|
||||
help='A comma-separated list of browsers to run the Selenium tests against.',
|
||||
|
@ -467,9 +461,6 @@ if __name__ == "__main__":
|
|||
os.environ['DJANGO_SETTINGS_MODULE'] = 'test_sqlite'
|
||||
options.settings = os.environ['DJANGO_SETTINGS_MODULE']
|
||||
|
||||
if options.liveserver is not None:
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options.liveserver
|
||||
|
||||
if options.selenium:
|
||||
if not options.tags:
|
||||
options.tags = ['selenium']
|
||||
|
|
|
@ -9,7 +9,6 @@ import errno
|
|||
import os
|
||||
import socket
|
||||
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.test import LiveServerTestCase, override_settings
|
||||
from django.utils._os import upath
|
||||
from django.utils.http import urlencode
|
||||
|
@ -44,55 +43,13 @@ class LiveServerBase(LiveServerTestCase):
|
|||
|
||||
|
||||
class LiveServerAddress(LiveServerBase):
|
||||
"""
|
||||
Ensure that the address set in the environment variable is valid.
|
||||
Refs #2879.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Backup original environment variable
|
||||
address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
|
||||
old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
|
||||
|
||||
# Just the host is not accepted
|
||||
cls.raises_exception('localhost', ImproperlyConfigured)
|
||||
|
||||
# The host must be valid
|
||||
cls.raises_exception('blahblahblah:8081', socket.error)
|
||||
|
||||
# The list of ports must be in a valid format
|
||||
cls.raises_exception('localhost:8081,', ImproperlyConfigured)
|
||||
cls.raises_exception('localhost:8081,blah', ImproperlyConfigured)
|
||||
cls.raises_exception('localhost:8081-', ImproperlyConfigured)
|
||||
cls.raises_exception('localhost:8081-blah', ImproperlyConfigured)
|
||||
cls.raises_exception('localhost:8081-8082-8083', ImproperlyConfigured)
|
||||
|
||||
# Restore original environment variable
|
||||
if address_predefined:
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
|
||||
else:
|
||||
del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
|
||||
|
||||
super(LiveServerAddress, cls).setUpClass()
|
||||
# put it in a list to prevent descriptor lookups in test
|
||||
cls.live_server_url_test = [cls.live_server_url]
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# skip it, as setUpClass doesn't call its parent either
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def raises_exception(cls, address, exception):
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = address
|
||||
try:
|
||||
super(LiveServerAddress, cls).setUpClass()
|
||||
raise Exception("The line above should have raised an exception")
|
||||
except exception:
|
||||
pass
|
||||
finally:
|
||||
super(LiveServerAddress, cls).tearDownClass()
|
||||
|
||||
def test_live_server_url_is_class_property(self):
|
||||
self.assertIsInstance(self.live_server_url_test[0], text_type)
|
||||
self.assertEqual(self.live_server_url_test[0], self.live_server_url)
|
||||
|
|
|
@ -44,35 +44,26 @@ class StaticLiveServerChecks(LiveServerBase):
|
|||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# Backup original environment variable
|
||||
address_predefined = 'DJANGO_LIVE_TEST_SERVER_ADDRESS' in os.environ
|
||||
old_address = os.environ.get('DJANGO_LIVE_TEST_SERVER_ADDRESS')
|
||||
|
||||
# If contrib.staticfiles isn't configured properly, the exception
|
||||
# should bubble up to the main thread.
|
||||
old_STATIC_URL = TEST_SETTINGS['STATIC_URL']
|
||||
TEST_SETTINGS['STATIC_URL'] = None
|
||||
cls.raises_exception('localhost:8081', ImproperlyConfigured)
|
||||
cls.raises_exception()
|
||||
TEST_SETTINGS['STATIC_URL'] = old_STATIC_URL
|
||||
|
||||
# Restore original environment variable
|
||||
if address_predefined:
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = old_address
|
||||
else:
|
||||
del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# skip it, as setUpClass doesn't call its parent either
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def raises_exception(cls, address, exception):
|
||||
os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = address
|
||||
def raises_exception(cls):
|
||||
try:
|
||||
super(StaticLiveServerChecks, cls).setUpClass()
|
||||
raise Exception("The line above should have raised an exception")
|
||||
except exception:
|
||||
except ImproperlyConfigured:
|
||||
# This raises ImproperlyConfigured("You're using the staticfiles
|
||||
# app without having set the required STATIC_URL setting.")
|
||||
pass
|
||||
finally:
|
||||
super(StaticLiveServerChecks, cls).tearDownClass()
|
||||
|
|
Loading…
Reference in New Issue