Commit 81cdcb66 authored by Tim Graham's avatar Tim Graham Committed by GitHub
Browse files

Fixed #26791 -- Replaced LiveServerTestCase port ranges with binding to port 0.

parent b5a1c3a6
Loading
Loading
Loading
Loading
+0 −11
Original line number Diff line number Diff line
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)

+10 −60
Original line number Diff line number Diff line
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,
        )
+0 −6
Original line number Diff line number Diff line
@@ -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
+9 −0
Original line number Diff line number Diff line
@@ -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
-------------

+7 −30
Original line number Diff line number Diff line
@@ -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
Loading