diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py index 73796fe417f..33a07620293 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 c1e9379d553..97e2b865460 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 a7837679815..3a0b05516e6 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 9106614d9cf..dc8b657a13b 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 aa0c412531e..1473f06f6af 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`):: - - 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 fd7b7869bae..1376ea9203a 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 122cb73282c..07f2dc7a842 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -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'] diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 57573622b0b..aeddf86f627 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 2c9d7b71dba..1bbadb337b5 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()