summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Graham <timograham@gmail.com>2016-06-23 12:04:05 -0400
committerGitHub <noreply@github.com>2016-06-23 12:04:05 -0400
commit81cdcb66bc74a0768d13f0e18872d46739028e64 (patch)
treea7b0c5577f17242f992ebc308acfccc738635d0a
parentb5a1c3a6f50362b57603e1833e44bff5628dde3c (diff)
Fixed #26791 -- Replaced LiveServerTestCase port ranges with binding to port 0.
-rw-r--r--django/core/management/commands/test.py11
-rw-r--r--django/test/testcases.py70
-rw-r--r--docs/ref/django-admin.txt6
-rw-r--r--docs/releases/1.11.txt9
-rw-r--r--docs/topics/testing/tools.txt37
-rw-r--r--tests/admin_scripts/tests.py41
-rwxr-xr-xtests/runtests.py9
-rw-r--r--tests/servers/tests.py45
-rw-r--r--tests/staticfiles_tests/test_liveserver.py19
9 files changed, 32 insertions, 215 deletions
diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py
index 73796fe417..33a0762029 100644
--- a/django/core/management/commands/test.py
+++ b/django/core/management/commands/test.py
@@ -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)
diff --git a/django/test/testcases.py b/django/test/testcases.py
index c1e9379d55..97e2b86546 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -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,
)
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index a783767981..3a0b05516e 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -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
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index 9106614d9c..dc8b657a13 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -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
-------------
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index aa0c412531..1473f06f6a 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -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
diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py
index fd7b7869ba..1376ea9203 100644
--- a/tests/admin_scripts/tests.py
+++ b/tests/admin_scripts/tests.py
@@ -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
diff --git a/tests/runtests.py b/tests/runtests.py
index 122cb73282..07f2dc7a84 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -418,12 +418,6 @@ if __name__ == "__main__":
'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']
diff --git a/tests/servers/tests.py b/tests/servers/tests.py
index 57573622b0..aeddf86f62 100644
--- a/tests/servers/tests.py
+++ b/tests/servers/tests.py
@@ -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)
diff --git a/tests/staticfiles_tests/test_liveserver.py b/tests/staticfiles_tests/test_liveserver.py
index 2c9d7b71db..1bbadb337b 100644
--- a/tests/staticfiles_tests/test_liveserver.py
+++ b/tests/staticfiles_tests/test_liveserver.py
@@ -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()