diff --git a/django/test/simple.py b/django/test/simple.py index 092ef4bde2..7610e1cb2c 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -1,4 +1,7 @@ +import sys +import signal import unittest + from django.conf import settings from django.db.models import get_app, get_apps from django.test import _doctest as doctest @@ -11,22 +14,48 @@ TEST_MODULE = 'tests' doctestOutputChecker = OutputChecker() class DjangoTestRunner(unittest.TextTestRunner): - + def __init__(self, verbosity=0, failfast=False, **kwargs): super(DjangoTestRunner, self).__init__(verbosity=verbosity, **kwargs) self.failfast = failfast - + self._keyboard_interrupt_intercepted = False + + def run(self, *args, **kwargs): + """ + Runs the test suite after registering a custom signal handler + that triggers a graceful exit when Ctrl-C is pressed. + """ + self._default_keyboard_interrupt_handler = signal.signal(signal.SIGINT, + self._keyboard_interrupt_handler) + result = super(DjangoTestRunner, self).run(*args, **kwargs) + signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler) + return result + + def _keyboard_interrupt_handler(self, signal_number, stack_frame): + """ + Handles Ctrl-C by setting a flag that will stop the test run when + the currently running test completes. + """ + self._keyboard_interrupt_intercepted = True + sys.stderr.write(" ") + # 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) + def _makeResult(self): result = super(DjangoTestRunner, self)._makeResult() failfast = self.failfast - + def stoptest_override(func): def stoptest(test): - if failfast and not result.wasSuccessful(): + # If we were set to failfast and the unit test failed, + # or if the user has typed Ctrl-C, report and quit + if (failfast and not result.wasSuccessful()) or \ + self._keyboard_interrupt_intercepted: result.stop() func(test) return stoptest - + setattr(result, 'stopTest', stoptest_override(result.stopTest)) return result diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 2cd879423c..88715ebdc7 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -812,12 +812,10 @@ test Runs tests for all installed models. See :ref:`topics-testing` for more information. ---failfast -~~~~~~~~~~ - .. versionadded:: 1.2 +.. django-admin-option:: --failfast -Use the ``--failfast`` option to stop running tests and report the failure +Use the :djadminopt:`--failfast` option to stop running tests and report the failure immediately after a test fails. testserver diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt index 8810963eaa..252682a3cf 100644 --- a/docs/releases/1.2.txt +++ b/docs/releases/1.2.txt @@ -471,10 +471,12 @@ Models can now use a 64 bit :class:`~django.db.models.BigIntegerField` type. Fast Failure for Tests ---------------------- -The ``test`` subcommand of ``django-admin.py``, and the ``runtests.py`` script -used to run Django's own test suite, support a new ``--failfast`` option. -When specified, this option causes the test runner to exit after -encountering a failure instead of continuing with the test run. +The :djadmin:`test` subcommand of ``django-admin.py``, and the ``runtests.py`` +script used to run Django's own test suite, support a new ``--failfast`` option. +When specified, this option causes the test runner to exit after encountering +a failure instead of continuing with the test run. In addition, the handling +of ``Ctrl-C`` during a test run has been improved to trigger a graceful exit +from the test run that reports details of the tests run before the interruption. Improved localization --------------------- diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 9ef6cceae8..f4881e2e32 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -242,8 +242,8 @@ in different circumstances. Running tests ============= -Once you've written tests, run them using your project's ``manage.py`` -utility:: +Once you've written tests, run them using the :djadmin:`test` subcommand of +your project's ``manage.py`` utility:: $ ./manage.py test @@ -274,6 +274,23 @@ a test case, add the name of the test method to the label:: $ ./manage.py test animals.AnimalTestCase.testFluffyAnimals +.. versionadded:: 1.2 + 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 +wait fur the currently running test to complete and then exit gracefully. +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 +``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 +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 +can press ``Ctrl-C`` a second time and the test run will halt immediately, +but not gracefully. No details of the tests run before the interruption will +be reported, and any test databases created by the run will not be destroyed. + The test database -----------------