web/lib/django/test/simple.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
       
     1 import sys
       
     2 import signal
     1 import unittest
     3 import unittest
       
     4 
     2 from django.conf import settings
     5 from django.conf import settings
     3 from django.db.models import get_app, get_apps
     6 from django.db.models import get_app, get_apps
     4 from django.test import _doctest as doctest
     7 from django.test import _doctest as doctest
     5 from django.test.utils import setup_test_environment, teardown_test_environment
     8 from django.test.utils import setup_test_environment, teardown_test_environment
     6 from django.test.testcases import OutputChecker, DocTestRunner, TestCase
     9 from django.test.testcases import OutputChecker, DocTestRunner, TestCase
     7 
    10 
     8 # The module name for tests outside models.py
    11 # The module name for tests outside models.py
     9 TEST_MODULE = 'tests'
    12 TEST_MODULE = 'tests'
    10 
    13 
    11 doctestOutputChecker = OutputChecker()
    14 doctestOutputChecker = OutputChecker()
       
    15 
       
    16 class DjangoTestRunner(unittest.TextTestRunner):
       
    17 
       
    18     def __init__(self, verbosity=0, failfast=False, **kwargs):
       
    19         super(DjangoTestRunner, self).__init__(verbosity=verbosity, **kwargs)
       
    20         self.failfast = failfast
       
    21         self._keyboard_interrupt_intercepted = False
       
    22 
       
    23     def run(self, *args, **kwargs):
       
    24         """
       
    25         Runs the test suite after registering a custom signal handler
       
    26         that triggers a graceful exit when Ctrl-C is pressed.
       
    27         """
       
    28         self._default_keyboard_interrupt_handler = signal.signal(signal.SIGINT,
       
    29             self._keyboard_interrupt_handler)
       
    30         try:
       
    31             result = super(DjangoTestRunner, self).run(*args, **kwargs)
       
    32         finally:
       
    33             signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
       
    34         return result
       
    35 
       
    36     def _keyboard_interrupt_handler(self, signal_number, stack_frame):
       
    37         """
       
    38         Handles Ctrl-C by setting a flag that will stop the test run when
       
    39         the currently running test completes.
       
    40         """
       
    41         self._keyboard_interrupt_intercepted = True
       
    42         sys.stderr.write(" <Test run halted by Ctrl-C> ")
       
    43         # Set the interrupt handler back to the default handler, so that
       
    44         # another Ctrl-C press will trigger immediate exit.
       
    45         signal.signal(signal.SIGINT, self._default_keyboard_interrupt_handler)
       
    46 
       
    47     def _makeResult(self):
       
    48         result = super(DjangoTestRunner, self)._makeResult()
       
    49         failfast = self.failfast
       
    50 
       
    51         def stoptest_override(func):
       
    52             def stoptest(test):
       
    53                 # If we were set to failfast and the unit test failed,
       
    54                 # or if the user has typed Ctrl-C, report and quit
       
    55                 if (failfast and not result.wasSuccessful()) or \
       
    56                     self._keyboard_interrupt_intercepted:
       
    57                     result.stop()
       
    58                 func(test)
       
    59             return stoptest
       
    60 
       
    61         setattr(result, 'stopTest', stoptest_override(result.stopTest))
       
    62         return result
    12 
    63 
    13 def get_tests(app_module):
    64 def get_tests(app_module):
    14     try:
    65     try:
    15         app_path = app_module.__name__.split('.')[:-1]
    66         app_path = app_module.__name__.split('.')[:-1]
    16         test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
    67         test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
    71                 # No doc tests in tests.py
   122                 # No doc tests in tests.py
    72                 pass
   123                 pass
    73     return suite
   124     return suite
    74 
   125 
    75 def build_test(label):
   126 def build_test(label):
    76     """Construct a test case a test with the specified label. Label should
   127     """Construct a test case with the specified label. Label should be of the
    77     be of the form model.TestClass or model.TestClass.test_method. Returns
   128     form model.TestClass or model.TestClass.test_method. Returns an
    78     an instantiated test or test suite corresponding to the label provided.
   129     instantiated test or test suite corresponding to the label provided.
    79 
   130 
    80     """
   131     """
    81     parts = label.split('.')
   132     parts = label.split('.')
    82     if len(parts) < 2 or len(parts) > 3:
   133     if len(parts) < 2 or len(parts) > 3:
    83         raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
   134         raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
    84 
   135 
       
   136     #
       
   137     # First, look for TestCase instances with a name that matches
       
   138     #
    85     app_module = get_app(parts[0])
   139     app_module = get_app(parts[0])
       
   140     test_module = get_tests(app_module)
    86     TestClass = getattr(app_module, parts[1], None)
   141     TestClass = getattr(app_module, parts[1], None)
    87 
   142 
    88     # Couldn't find the test class in models.py; look in tests.py
   143     # Couldn't find the test class in models.py; look in tests.py
    89     if TestClass is None:
   144     if TestClass is None:
    90         test_module = get_tests(app_module)
       
    91         if test_module:
   145         if test_module:
    92             TestClass = getattr(test_module, parts[1], None)
   146             TestClass = getattr(test_module, parts[1], None)
    93 
   147 
    94     if len(parts) == 2: # label is app.TestClass
   148     try:
       
   149         if issubclass(TestClass, unittest.TestCase):
       
   150             if len(parts) == 2: # label is app.TestClass
       
   151                 try:
       
   152                     return unittest.TestLoader().loadTestsFromTestCase(TestClass)
       
   153                 except TypeError:
       
   154                     raise ValueError("Test label '%s' does not refer to a test class" % label)
       
   155             else: # label is app.TestClass.test_method
       
   156                 return TestClass(parts[2])
       
   157     except TypeError:
       
   158         # TestClass isn't a TestClass - it must be a method or normal class
       
   159         pass
       
   160 
       
   161     #
       
   162     # If there isn't a TestCase, look for a doctest that matches
       
   163     #
       
   164     tests = []
       
   165     for module in app_module, test_module:
    95         try:
   166         try:
    96             return unittest.TestLoader().loadTestsFromTestCase(TestClass)
   167             doctests = doctest.DocTestSuite(module,
    97         except TypeError:
   168                                             checker=doctestOutputChecker,
    98             raise ValueError("Test label '%s' does not refer to a test class" % label)
   169                                             runner=DocTestRunner)
    99     else: # label is app.TestClass.test_method
   170             # Now iterate over the suite, looking for doctests whose name
   100         if not TestClass:
   171             # matches the pattern that was given
   101             raise ValueError("Test label '%s' does not refer to a test class" % label)
   172             for test in doctests:
   102         return TestClass(parts[2])
   173                 if test._dt_test.name in (
   103 
   174                         '%s.%s' % (module.__name__, '.'.join(parts[1:])),
   104 # Python 2.3 compatibility: TestSuites were made iterable in 2.4.
   175                         '%s.__test__.%s' % (module.__name__, '.'.join(parts[1:]))):
   105 # We need to iterate over them, so we add the missing method when
   176                     tests.append(test)
   106 # necessary.
   177         except ValueError:
   107 try:
   178             # No doctests found.
   108     getattr(unittest.TestSuite, '__iter__')
   179             pass
   109 except AttributeError:
   180 
   110     setattr(unittest.TestSuite, '__iter__', lambda s: iter(s._tests))
   181     # If no tests were found, then we were given a bad test label.
       
   182     if not tests:
       
   183         raise ValueError("Test label '%s' does not refer to a test" % label)
       
   184 
       
   185     # Construct a suite out of the tests that matched.
       
   186     return unittest.TestSuite(tests)
   111 
   187 
   112 def partition_suite(suite, classes, bins):
   188 def partition_suite(suite, classes, bins):
   113     """
   189     """
   114     Partitions a test suite by test type.
   190     Partitions a test suite by test type.
   115 
   191 
   144     partition_suite(suite, classes, bins)
   220     partition_suite(suite, classes, bins)
   145     for i in range(class_count):
   221     for i in range(class_count):
   146         bins[0].addTests(bins[i+1])
   222         bins[0].addTests(bins[i+1])
   147     return bins[0]
   223     return bins[0]
   148 
   224 
   149 def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]):
   225 
   150     """
   226 class DjangoTestSuiteRunner(object):
   151     Run the unit tests for all the test labels in the provided list.
   227     def __init__(self, verbosity=1, interactive=True, failfast=True, **kwargs):
   152     Labels must be of the form:
   228         self.verbosity = verbosity
   153      - app.TestClass.test_method
   229         self.interactive = interactive
   154         Run a single specific test method
   230         self.failfast = failfast
   155      - app.TestClass
   231 
   156         Run all the test methods in a given class
   232     def setup_test_environment(self, **kwargs):
   157      - app
   233         setup_test_environment()
   158         Search for doctests and unittests in the named application.
   234         settings.DEBUG = False
   159 
   235 
   160     When looking for tests, the test runner will look in the models and
   236     def build_suite(self, test_labels, extra_tests=None, **kwargs):
   161     tests modules for the application.
   237         suite = unittest.TestSuite()
   162 
   238 
   163     A list of 'extra' tests may also be provided; these tests
   239         if test_labels:
   164     will be added to the test suite.
   240             for label in test_labels:
   165 
   241                 if '.' in label:
   166     Returns the number of tests that failed.
   242                     suite.addTest(build_test(label))
   167     """
   243                 else:
   168     setup_test_environment()
   244                     app = get_app(label)
   169 
   245                     suite.addTest(build_suite(app))
   170     settings.DEBUG = False
   246         else:
   171     suite = unittest.TestSuite()
   247             for app in get_apps():
   172 
   248                 suite.addTest(build_suite(app))
   173     if test_labels:
   249 
   174         for label in test_labels:
   250         if extra_tests:
   175             if '.' in label:
   251             for test in extra_tests:
   176                 suite.addTest(build_test(label))
   252                 suite.addTest(test)
       
   253 
       
   254         return reorder_suite(suite, (TestCase,))
       
   255 
       
   256     def setup_databases(self, **kwargs):
       
   257         from django.db import connections
       
   258         old_names = []
       
   259         mirrors = []
       
   260         for alias in connections:
       
   261             connection = connections[alias]
       
   262             # If the database is a test mirror, redirect it's connection
       
   263             # instead of creating a test database.
       
   264             if connection.settings_dict['TEST_MIRROR']:
       
   265                 mirrors.append((alias, connection))
       
   266                 mirror_alias = connection.settings_dict['TEST_MIRROR']
       
   267                 connections._connections[alias] = connections[mirror_alias]
   177             else:
   268             else:
   178                 app = get_app(label)
   269                 old_names.append((connection, connection.settings_dict['NAME']))
   179                 suite.addTest(build_suite(app))
   270                 connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive)
   180     else:
   271         return old_names, mirrors
   181         for app in get_apps():
   272 
   182             suite.addTest(build_suite(app))
   273     def run_suite(self, suite, **kwargs):
   183 
   274         return DjangoTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
   184     for test in extra_tests:
   275 
   185         suite.addTest(test)
   276     def teardown_databases(self, old_config, **kwargs):
   186 
   277         from django.db import connections
   187     suite = reorder_suite(suite, (TestCase,))
   278         old_names, mirrors = old_config
   188 
   279         # Point all the mirrors back to the originals
   189     old_name = settings.DATABASE_NAME
   280         for alias, connection in mirrors:
   190     from django.db import connection
   281             connections._connections[alias] = connection
   191     connection.creation.create_test_db(verbosity, autoclobber=not interactive)
   282         # Destroy all the non-mirror databases
   192     result = unittest.TextTestRunner(verbosity=verbosity).run(suite)
   283         for connection, old_name in old_names:
   193     connection.creation.destroy_test_db(old_name, verbosity)
   284             connection.creation.destroy_test_db(old_name, self.verbosity)
   194 
   285 
   195     teardown_test_environment()
   286     def teardown_test_environment(self, **kwargs):
   196 
   287         teardown_test_environment()
   197     return len(result.failures) + len(result.errors)
   288 
       
   289     def suite_result(self, suite, result, **kwargs):
       
   290         return len(result.failures) + len(result.errors)
       
   291 
       
   292     def run_tests(self, test_labels, extra_tests=None, **kwargs):
       
   293         """
       
   294         Run the unit tests for all the test labels in the provided list.
       
   295         Labels must be of the form:
       
   296          - app.TestClass.test_method
       
   297             Run a single specific test method
       
   298          - app.TestClass
       
   299             Run all the test methods in a given class
       
   300          - app
       
   301             Search for doctests and unittests in the named application.
       
   302 
       
   303         When looking for tests, the test runner will look in the models and
       
   304         tests modules for the application.
       
   305 
       
   306         A list of 'extra' tests may also be provided; these tests
       
   307         will be added to the test suite.
       
   308 
       
   309         Returns the number of tests that failed.
       
   310         """
       
   311         self.setup_test_environment()
       
   312         suite = self.build_suite(test_labels, extra_tests)
       
   313         old_config = self.setup_databases()
       
   314         result = self.run_suite(suite)
       
   315         self.teardown_databases(old_config)
       
   316         self.teardown_test_environment()
       
   317         return self.suite_result(suite, result)
       
   318 
       
   319 def run_tests(test_labels, verbosity=1, interactive=True, failfast=False, extra_tests=None):
       
   320     import warnings
       
   321     warnings.warn(
       
   322         'The run_tests() test runner has been deprecated in favor of DjangoTestSuiteRunner.',
       
   323         PendingDeprecationWarning
       
   324     )
       
   325     test_runner = DjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast)
       
   326     return test_runner.run_tests(test_labels, extra_tests=extra_tests)