Author: russellm
Date: 2010-01-18 09:11:01 -0600 (Mon, 18 Jan 2010)
New Revision: 12255

Modified:
   django/trunk/django/conf/global_settings.py
   django/trunk/django/core/management/commands/test.py
   django/trunk/django/test/simple.py
   django/trunk/docs/internals/deprecation.txt
   django/trunk/docs/releases/1.2.txt
   django/trunk/docs/topics/testing.txt
   django/trunk/tests/runtests.py
Log:
Fixed #12624 -- Modified test runners to be class based.

Modified: django/trunk/django/conf/global_settings.py
===================================================================
--- django/trunk/django/conf/global_settings.py 2010-01-18 13:02:07 UTC (rev 
12254)
+++ django/trunk/django/conf/global_settings.py 2010-01-18 15:11:01 UTC (rev 
12255)
@@ -487,8 +487,8 @@
 # TESTING #
 ###########
 
-# The name of the method to use to invoke the test suite
-TEST_RUNNER = 'django.test.simple.run_tests'
+# The name of the class to use to run the test suite
+TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
 
 # The name of the database to use for testing purposes.
 # If None, a name of 'test_' + DATABASE_NAME will be assumed

Modified: django/trunk/django/core/management/commands/test.py
===================================================================
--- django/trunk/django/core/management/commands/test.py        2010-01-18 
13:02:07 UTC (rev 12254)
+++ django/trunk/django/core/management/commands/test.py        2010-01-18 
15:11:01 UTC (rev 12255)
@@ -21,14 +21,20 @@
         verbosity = int(options.get('verbosity', 1))
         interactive = options.get('interactive', True)
         failfast = options.get('failfast', False)
-        test_runner = get_runner(settings)
+        TestRunner = get_runner(settings)
 
-        # Some custom test runners won't accept the failfast flag, so let's 
make sure they accept it before passing it to them
-        if 'failfast' in test_runner.func_code.co_varnames:
-            failures = test_runner(test_labels, verbosity=verbosity, 
interactive=interactive,
-                                   failfast=failfast)
+        if hasattr(TestRunner, 'func_name'):
+            # Pre 1.2 test runners were just functions,
+            # and did not support the 'failfast' option.
+            import warnings
+            warnings.warn(
+                'Function-based test runners are deprecated. Test runners 
should be classes with a run_tests() method.',
+                PendingDeprecationWarning
+            )
+            failures = TestRunner(test_labels, verbosity=verbosity, 
interactive=interactive)
         else:
-            failures = test_runner(test_labels, verbosity=verbosity, 
interactive=interactive)
+            test_runner = TestRunner(verbosity=verbosity, 
interactive=interactive, failfast=failfast)
+            failures = test_runner.run_tests(test_labels)
 
         if failures:
             sys.exit(bool(failures))

Modified: django/trunk/django/test/simple.py
===================================================================
--- django/trunk/django/test/simple.py  2010-01-18 13:02:07 UTC (rev 12254)
+++ django/trunk/django/test/simple.py  2010-01-18 15:11:01 UTC (rev 12255)
@@ -40,7 +40,7 @@
         """
         self._keyboard_interrupt_intercepted = True
         sys.stderr.write(" <Test run halted by Ctrl-C> ")
-        # Set the interrupt handler back to the default handler, so that 
+        # Set the interrupt handler back to the default handler, so that
         # another Ctrl-C press will trigger immediate exit.
         signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
 
@@ -197,56 +197,97 @@
         bins[0].addTests(bins[i+1])
     return bins[0]
 
-def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, 
extra_tests=[]):
-    """
-    Run the unit tests for all the test labels in the provided list.
-    Labels must be of the form:
-     - app.TestClass.test_method
-        Run a single specific test method
-     - app.TestClass
-        Run all the test methods in a given class
-     - app
-        Search for doctests and unittests in the named application.
 
-    When looking for tests, the test runner will look in the models and
-    tests modules for the application.
+class DjangoTestSuiteRunner(object):
+    def __init__(self, verbosity=1, interactive=True, failfast=True):
+        self.verbosity = verbosity
+        self.interactive = interactive
+        self.failfast = failfast
 
-    A list of 'extra' tests may also be provided; these tests
-    will be added to the test suite.
+    def setup_test_environment(self):
+        setup_test_environment()
+        settings.DEBUG = False
 
-    Returns the number of tests that failed.
-    """
-    setup_test_environment()
+    def build_suite(self, test_labels, extra_tests=None):
+        suite = unittest.TestSuite()
 
-    settings.DEBUG = False
-    suite = unittest.TestSuite()
-
-    if test_labels:
-        for label in test_labels:
-            if '.' in label:
-                suite.addTest(build_test(label))
-            else:
-                app = get_app(label)
+        if test_labels:
+            for label in test_labels:
+                if '.' in label:
+                    suite.addTest(build_test(label))
+                else:
+                    app = get_app(label)
+                    suite.addTest(build_suite(app))
+        else:
+            for app in get_apps():
                 suite.addTest(build_suite(app))
-    else:
-        for app in get_apps():
-            suite.addTest(build_suite(app))
 
-    for test in extra_tests:
-        suite.addTest(test)
+        if extra_tests:
+            for test in extra_tests:
+                suite.addTest(test)
 
-    suite = reorder_suite(suite, (TestCase,))
+        return reorder_suite(suite, (TestCase,))
 
-    from django.db import connections
-    old_names = []
-    for alias in connections:
-        connection = connections[alias]
-        old_names.append((connection, connection.settings_dict['NAME']))
-        connection.creation.create_test_db(verbosity, autoclobber=not 
interactive)
-    result = DjangoTestRunner(verbosity=verbosity, 
failfast=failfast).run(suite)
-    for connection, old_name in old_names:
-        connection.creation.destroy_test_db(old_name, verbosity)
+    def setup_databases(self):
+        from django.db import connections
+        old_names = []
+        for alias in connections:
+            connection = connections[alias]
+            old_names.append((connection, connection.settings_dict['NAME']))
+            connection.creation.create_test_db(self.verbosity, autoclobber=not 
self.interactive)
+        return old_names
 
-    teardown_test_environment()
+    def run_suite(self, suite):
+        return DjangoTestRunner(verbosity=self.verbosity, 
failfast=self.failfast).run(suite)
 
-    return len(result.failures) + len(result.errors)
+    def teardown_databases(self, old_names):
+        for connection, old_name in old_names:
+            connection.creation.destroy_test_db(old_name, self.verbosity)
+
+    def teardown_test_environment(self):
+        teardown_test_environment()
+
+    def suite_result(self, result):
+        return len(result.failures) + len(result.errors)
+
+    def run_tests(self, test_labels, extra_tests=None):
+        """
+        Run the unit tests for all the test labels in the provided list.
+        Labels must be of the form:
+         - app.TestClass.test_method
+            Run a single specific test method
+         - app.TestClass
+            Run all the test methods in a given class
+         - app
+            Search for doctests and unittests in the named application.
+
+        When looking for tests, the test runner will look in the models and
+        tests modules for the application.
+
+        A list of 'extra' tests may also be provided; these tests
+        will be added to the test suite.
+
+        Returns the number of tests that failed.
+        """
+        self.setup_test_environment()
+
+        old_names = self.setup_databases()
+
+        suite = self.build_suite(test_labels, extra_tests)
+
+        result = self.run_suite(suite)
+
+        self.teardown_databases(old_names)
+
+        self.teardown_test_environment()
+
+        return self.suite_result(result)
+
+def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, 
extra_tests=None):
+    import warnings
+    warnings.warn(
+        'The run_tests() test runner has been deprecated in favor of 
DjangoTestSuiteRunner.',
+        PendingDeprecationWarning
+    )
+    test_runner = DjangoTestSuiteRunner(verbosity=verbosity, 
interactive=interactive, failfast=failfast)
+    return test_runner.run_tests(test_labels, extra_tests=extra_tests)

Modified: django/trunk/docs/internals/deprecation.txt
===================================================================
--- django/trunk/docs/internals/deprecation.txt 2010-01-18 13:02:07 UTC (rev 
12254)
+++ django/trunk/docs/internals/deprecation.txt 2010-01-18 15:11:01 UTC (rev 
12255)
@@ -74,6 +74,9 @@
           ``django.utils.formats.get_format()`` to get the appropriate
           formats.
 
+        * The ability to use a function-based test runners will be removed,
+          along with the ``django.test.simple.run_tests()`` test runner.
+
     * 2.0
         * ``django.views.defaults.shortcut()``. This function has been moved
           to ``django.contrib.contenttypes.views.shortcut()`` as part of the

Modified: django/trunk/docs/releases/1.2.txt
===================================================================
--- django/trunk/docs/releases/1.2.txt  2010-01-18 13:02:07 UTC (rev 12254)
+++ django/trunk/docs/releases/1.2.txt  2010-01-18 15:11:01 UTC (rev 12255)
@@ -367,6 +367,14 @@
 django.form.fields to django.core.validators. You will need to update
 your imports if you are using it.
 
+Function-based test runners
+---------------------------
+
+Django 1.2 changes the test runner tools to use a class-based
+approach. Old style function-based test runners will still work, but
+should be updated to use the new :ref:`class-based runners
+<topics-testing-test_runner>`.
+
 What's new in Django 1.2
 ========================
 
@@ -428,7 +436,7 @@
 .. code-block:: html+django
 
     {% ifnotequal a b %}
-     ...
+    ...
     {% endifnotequal %}
 
 You can now do this::
@@ -436,7 +444,7 @@
 .. code-block:: html+django
 
     {% if a != b %}
-     ...
+    ...
     {% endif %}
 
 There's really no reason to use ``{% ifequal %}`` or ``{% ifnotequal %}``

Modified: django/trunk/docs/topics/testing.txt
===================================================================
--- django/trunk/docs/topics/testing.txt        2010-01-18 13:02:07 UTC (rev 
12254)
+++ django/trunk/docs/topics/testing.txt        2010-01-18 15:11:01 UTC (rev 
12255)
@@ -275,15 +275,15 @@
     $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals
 
 .. versionadded:: 1.2
-   You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``. 
+   You can now trigger a graceful exit from a test run by pressing ``Ctrl-C``.
 
-If you press ``Ctrl-C`` while the tests are running, the test runner will 
+If you press ``Ctrl-C`` while the tests are running, the test runner will
 wait for the currently running test to complete and then exit gracefully.
-During a graceful exit the test runner will output details of any test 
+During a graceful exit the test runner will output details of any test
 failures, report on how many tests were run and how many errors and failures
-were encountered, and destroy any test databases as usual. Thus pressing 
+were encountered, and destroy any test databases as usual. Thus pressing
 ``Ctrl-C`` can be very useful if you forget to pass the 
:djadminopt:`--failfast`
-option, notice that some tests are unexpectedly failing, and want to get 
details 
+option, notice that some tests are unexpectedly failing, and want to get 
details
 on the failures without waiting for the full test run to complete.
 
 If you do not want to wait for the currently running test to finish, you
@@ -1228,42 +1228,66 @@
 
 When you run ``./manage.py test``, Django looks at the :setting:`TEST_RUNNER`
 setting to determine what to do. By default, :setting:`TEST_RUNNER` points to
-``'django.test.simple.run_tests'``. This method defines the default Django
+``'django.test.simple.DjangoTestSuiteRunner'``. This class defines the default 
Django
 testing behavior. This behavior involves:
 
     #. Performing global pre-test setup.
 
-    #. Creating the test database.
+    #. Creating the test databases.
 
     #. Running ``syncdb`` to install models and initial data into the test
-       database.
+       databases.
 
     #. Looking for unit tests and doctests in the ``models.py`` and
        ``tests.py`` files in each installed application.
 
     #. Running the unit tests and doctests that are found.
 
-    #. Destroying the test database.
+    #. Destroying the test databases.
 
     #. Performing global post-test teardown.
 
 If you define your own test runner method and point :setting:`TEST_RUNNER` at
 that method, Django will execute your test runner whenever you run
 ``./manage.py test``. In this way, it is possible to use any test framework
-that can be executed from Python code.
+that can be executed from Python code, or to modify the Django test execution
+process to satisfy whatever testing requirements you may have.
 
+.. _topics-testing-test_runner:
+
 Defining a test runner
 ----------------------
 
-.. versionadded:: 1.0
+.. versionchanged:: 1.2
+   Prior to 1.2, test runners were a single function, not a class.
 
 .. currentmodule:: django.test.simple
 
-By convention, a test runner should be called ``run_tests``. The only strict
-requirement is that it has the same arguments as the Django test runner:
+A test runner is a class defining a ``run_tests()`` method. Django ships
+with a ``DjangoTestSuiteRunner`` class that defines the default Django
+testing behavior. This class defines the ``run_tests()`` entry point,
+plus a selection of other methods that are used to by ``run_tests()`` to
+set up, execute and tear down the test suite.
 
-.. function:: run_tests(test_labels, verbosity=1, interactive=True, 
extra_tests=[])
+.. class:: DjangoTestSuiteRunner(verbosity=1, interactive=True, failfast=True)
 
+    ``verbosity`` determines the amount of notification and debug information
+    that will be printed to the console; ``0`` is no output, ``1`` is normal
+    output, and ``2`` is verbose output.
+
+    If ``interactive`` is ``True``, the test suite has permission to ask the
+    user for instructions when the test suite is executed. An example of this
+    behavior would be asking for permission to delete an existing test
+    database. If ``interactive`` is ``False``, the test suite must be able to
+    run without any manual intervention.
+
+    If ``failfast`` is ``True``, the test suite will stop running after the
+    first test failure is detected.
+
+.. method:: DjangoTestSuiteRunner.run_tests(test_labels, extra_tests=[])
+
+    Run the test suite.
+
     ``test_labels`` is a list of strings describing the tests to be run. A test
     label can take one of three forms:
 
@@ -1275,22 +1299,62 @@
     If ``test_labels`` has a value of ``None``, the test runner should run
     search for tests in all the applications in :setting:`INSTALLED_APPS`.
 
-    ``verbosity`` determines the amount of notification and debug information
-    that will be printed to the console; ``0`` is no output, ``1`` is normal
-    output, and ``2`` is verbose output.
+    ``extra_tests`` is a list of extra ``TestCase`` instances to add to the
+    suite that is executed by the test runner. These extra tests are run
+    in addition to those discovered in the modules listed in ``test_labels``.
 
-    If ``interactive`` is ``True``, the test suite has permission to ask the
-    user for instructions when the test suite is executed. An example of this
-    behavior would be asking for permission to delete an existing test
-    database. If ``interactive`` is ``False``, the test suite must be able to
-    run without any manual intervention.
+    This method should return the number of tests that failed.
 
+.. method:: DjangoTestSuiteRunner.setup_test_environment()
+
+    Sets up the test environment ready for testing.
+
+.. method:: DjangoTestSuiteRunner.build_suite(test_labels, extra_tests=[])
+
+    Constructs a test suite that matches the test labels provided.
+
+    ``test_labels`` is a list of strings describing the tests to be run. A test
+    label can take one of three forms:
+
+        * ``app.TestCase.test_method`` -- Run a single test method in a test
+          case.
+        * ``app.TestCase`` -- Run all the test methods in a test case.
+        * ``app`` -- Search for and run all tests in the named application.
+
+    If ``test_labels`` has a value of ``None``, the test runner should run
+    search for tests in all the applications in :setting:`INSTALLED_APPS`.
+
     ``extra_tests`` is a list of extra ``TestCase`` instances to add to the
     suite that is executed by the test runner. These extra tests are run
-    in addition to those discovered in the modules listed in ``module_list``.
+    in addition to those discovered in the modules listed in ``test_labels``.
 
-    This method should return the number of tests that failed.
+    Returns a ``TestSuite`` instance ready to be run.
 
+.. method:: DjangoTestSuiteRunner.setup_databases()
+
+    Creates the test databases.
+
+    Returns the list of old database names that will need to be restored
+
+.. method:: DjangoTestSuiteRunner.run_suite(suite)
+
+    Runs the test suite.
+
+    Returns the result produced by the running the test suite.
+
+.. method:: DjangoTestSuiteRunner.teardown_databases(old_names)
+
+    Destroys the test databases, restoring the old names.
+
+.. method:: DjangoTestSuiteRunner.teardown_test_environment()
+
+    Restores the pre-test environment.
+
+.. method:: DjangoTestSuiteRunner.suite_result(result)
+
+    Computes and returns a return code based on a test suite result.
+
+
 Testing utilities
 -----------------
 

Modified: django/trunk/tests/runtests.py
===================================================================
--- django/trunk/tests/runtests.py      2010-01-18 13:02:07 UTC (rev 12254)
+++ django/trunk/tests/runtests.py      2010-01-18 15:11:01 UTC (rev 12255)
@@ -156,11 +156,23 @@
     # Run the test suite, including the extra validation tests.
     from django.test.utils import get_runner
     if not hasattr(settings, 'TEST_RUNNER'):
-        settings.TEST_RUNNER = 'django.test.simple.run_tests'
-    test_runner = get_runner(settings)
+        settings.TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
+    TestRunner = get_runner(settings)
 
-    failures = test_runner(test_labels, verbosity=verbosity, 
interactive=interactive, failfast=failfast,
-                           extra_tests=extra_tests)
+    if hasattr(TestRunner, 'func_name'):
+        # Pre 1.2 test runners were just functions,
+        # and did not support the 'failfast' option.
+        import warnings
+        warnings.warn(
+            'Function-based test runners are deprecated. Test runners should 
be classes with a run_tests() method.',
+            PendingDeprecationWarning
+        )
+        failures = TestRunner(test_labels, verbosity=verbosity, 
interactive=interactive,
+            extra_tests=extra_tests)
+    else:
+        test_runner = TestRunner(verbosity=verbosity, interactive=interactive, 
failfast=failfast)
+        failures = test_runner.run_tests(test_labels, extra_tests=extra_tests)
+
     if failures:
         sys.exit(bool(failures))
 

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.


Reply via email to