Commit 4b452429 authored by Claude Paroz's avatar Claude Paroz
Browse files

Converted test management command to argparse

Keeping backwards compatibility with test_runner.option_list is
tricky and would imply transforming an optparse.Option to an
argparse.Action. I choose to introduce a backwards incompatible
change because it only affects testing, not runtime behavior.
parent cbff097b
Loading
Loading
Loading
Loading
+33 −29
Original line number Diff line number Diff line
import logging
import sys
import os
from optparse import make_option, OptionParser

from django.conf import settings
from django.core.management.base import BaseCommand
@@ -9,26 +8,7 @@ from django.test.utils import get_runner


class Command(BaseCommand):
    option_list = BaseCommand.option_list + (
        make_option('--noinput',
            action='store_false', dest='interactive', default=True,
            help='Tells Django to NOT prompt the user for input of any kind.'),
        make_option('--failfast',
            action='store_true', dest='failfast', default=False,
            help='Tells Django to stop running the test suite after first '
                 'failed test.'),
        make_option('--testrunner',
            action='store', dest='testrunner',
            help='Tells Django to use specified test runner class instead of '
                 'the one specified by the TEST_RUNNER setting.'),
        make_option('--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.'),
    )
    help = ('Discover and run tests in the specified modules or the current directory.')
    args = '[path.to.modulename|path.to.modulename.TestCase|path.to.modulename.TestCase.test_method]...'
    help = 'Discover and run tests in the specified modules or the current directory.'

    requires_system_checks = False

@@ -49,15 +29,40 @@ class Command(BaseCommand):
                break
        super(Command, self).run_from_argv(argv)

    def create_parser(self, prog_name, subcommand):
        parser = super(Command, self).create_parser(prog_name, subcommand)
    def add_arguments(self, parser):
        parser.add_argument('args', metavar='test_label', nargs='*',
            help='Module paths to test; can be modulename, modulename.TestCase or modulename.TestCase.test_method')
        parser.add_argument('--noinput',
            action='store_false', dest='interactive', default=True,
            help='Tells Django to NOT prompt the user for input of any kind.'),
        parser.add_argument('--failfast',
            action='store_true', dest='failfast', default=False,
            help='Tells Django to stop running the test suite after first '
                 'failed test.'),
        parser.add_argument('--testrunner',
            action='store', dest='testrunner',
            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.'),

        test_runner_class = get_runner(settings, self.test_runner)
        for opt in getattr(test_runner_class, 'option_list', ()):
            parser.add_option(opt)
        return parser
        if hasattr(test_runner_class, 'option_list'):
            # Keeping compatibility with both optparse and argparse at this level
            # would be too heavy for a non-critical item
            raise RuntimeError(
                "The method to extend accepted command-line arguments by the "
                "test management command has changed in Django 1.8. Please "
                "create an add_arguments class method to achieve this.")

        if hasattr(test_runner_class, 'add_arguments'):
            test_runner_class.add_arguments(parser)

    def execute(self, *args, **options):
        if int(options['verbosity']) > 0:
        if options['verbosity'] > 0:
            # ensure that deprecation warnings are displayed during testing
            # the following state is assumed:
            # logging.capturewarnings is true
@@ -67,7 +72,7 @@ class Command(BaseCommand):
            handler = logging.StreamHandler()
            logger.addHandler(handler)
        super(Command, self).execute(*args, **options)
        if int(options['verbosity']) > 0:
        if options['verbosity'] > 0:
            # remove the testing-specific handler
            logger.removeHandler(handler)

@@ -76,7 +81,6 @@ class Command(BaseCommand):
        from django.test.utils import get_runner

        TestRunner = get_runner(settings, options.get('testrunner'))
        options['verbosity'] = int(options.get('verbosity'))

        if options.get('liveserver') is not None:
            os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options['liveserver']
+12 −12
Original line number Diff line number Diff line
from importlib import import_module
import os
from optparse import make_option
import unittest
from unittest import TestSuite, defaultTestLoader

@@ -19,17 +18,6 @@ class DiscoverRunner(object):
    test_runner = unittest.TextTestRunner
    test_loader = defaultTestLoader
    reorder_by = (TestCase, SimpleTestCase)
    option_list = (
        make_option('-t', '--top-level-directory',
            action='store', dest='top_level', default=None,
            help='Top level of project for unittest discovery.'),
        make_option('-p', '--pattern', action='store', dest='pattern',
            default="test*.py",
            help='The test matching pattern. Defaults to test*.py.'),
        make_option('-k', '--keepdb', action='store_true', dest='keepdb',
            default=False,
            help='Preserve the test DB between runs. Defaults to False'),
    )

    def __init__(self, pattern=None, top_level=None,
                 verbosity=1, interactive=True, failfast=False, keepdb=False,
@@ -43,6 +31,18 @@ class DiscoverRunner(object):
        self.failfast = failfast
        self.keepdb = keepdb

    @classmethod
    def add_arguments(cls, parser):
        parser.add_argument('-t', '--top-level-directory',
            action='store', dest='top_level', default=None,
            help='Top level of project for unittest discovery.')
        parser.add_argument('-p', '--pattern', action='store', dest='pattern',
            default="test*.py",
            help='The test matching pattern. Defaults to test*.py.')
        parser.add_argument('-k', '--keepdb', action='store_true', dest='keepdb',
            default=False,
            help='Preserve the test DB between runs. Defaults to False')

    def setup_test_environment(self, **kwargs):
        setup_test_environment()
        settings.DEBUG = False
+11 −0
Original line number Diff line number Diff line
@@ -294,6 +294,17 @@ you don't have to keep compatibility with older Django versions, it's better to
implement the new :meth:`~django.core.management.BaseCommand.add_arguments`
method as described in :doc:`/howto/custom-management-commands`.

Custom test management command arguments through test runner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The method to add custom arguments to the `test` management command through the
test runner has changed. Previously, you could provide an `option_list` class
variable on the test runner to add more arguments (à la :py:mod:`optparse`).
Now to implement the same behavior, you have to create an
``add_arguments(cls, parser)`` class method on the test runner and call
``parser.add_argument`` to add any custom arguments, as parser is now an
:py:class:`argparse.ArgumentParser` instance.

Miscellaneous
~~~~~~~~~~~~~

+24 −3
Original line number Diff line number Diff line
@@ -333,9 +333,15 @@ execute and tear down the test suite.
    runner, ensure it accepts ``**kwargs``.

    Your test runner may also define additional command-line options.
    If you add an ``option_list`` attribute to a subclassed test runner,
    those options will be added to the list of command-line options that
    the :djadmin:`test` command can use.
    Create or override an ``add_arguments(cls, parser)`` class method and add
    custom arguments by calling ``parser.add_argument()`` inside the method, so
    that the :djadmin:`test` command will be able to use those arguments.

    .. versionchanged:: 1.8

        Previously, you had to provide an ``option_list`` attribute to a
        subclassed test runner to add options to the list of command-line
        options that the :djadmin:`test` command could use.

Attributes
~~~~~~~~~~
@@ -372,6 +378,12 @@ Attributes
    management command's ``OptionParser`` for parsing arguments. See the
    documentation for Python's ``optparse`` module for more details.

    .. deprecated:: 1.8

        You should now override the :meth:`~DiscoverRunner.add_arguments` class
        method to add custom arguments accepted by the :djadmin:`test`
        management command.

Methods
~~~~~~~

@@ -389,6 +401,15 @@ Methods

    This method should return the number of tests that failed.

.. classmethod:: DiscoverRunner.add_arguments(parser)

    .. versionadded:: 1.8

    Override this class method to add custom arguments accepted by the
    :djadmin:`test` management command. See
    :py:meth:`argparse.ArgumentParser.add_argument()` for details about adding
    arguments to a parser.

.. method:: DiscoverRunner.setup_test_environment(**kwargs)

    Sets up the test environment by calling
+6 −6
Original line number Diff line number Diff line
@@ -3,7 +3,6 @@ Tests for django test runner
"""
from __future__ import unicode_literals

from optparse import make_option
import unittest

from django.core.exceptions import ImproperlyConfigured
@@ -152,11 +151,6 @@ class ManageCommandTests(unittest.TestCase):


class CustomOptionsTestRunner(runner.DiscoverRunner):
    option_list = (
        make_option('--option_a', '-a', action='store', dest='option_a', default='1'),
        make_option('--option_b', '-b', action='store', dest='option_b', default='2'),
        make_option('--option_c', '-c', action='store', dest='option_c', default='3'),
    )

    def __init__(self, verbosity=1, interactive=True, failfast=True, option_a=None, option_b=None, option_c=None, **kwargs):
        super(CustomOptionsTestRunner, self).__init__(verbosity=verbosity, interactive=interactive,
@@ -165,6 +159,12 @@ class CustomOptionsTestRunner(runner.DiscoverRunner):
        self.option_b = option_b
        self.option_c = option_c

    @classmethod
    def add_arguments(cls, parser):
        parser.add_argument('--option_a', '-a', action='store', dest='option_a', default='1'),
        parser.add_argument('--option_b', '-b', action='store', dest='option_b', default='2'),
        parser.add_argument('--option_c', '-c', action='store', dest='option_c', default='3'),

    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        print("%s:%s:%s" % (self.option_a, self.option_b, self.option_c))