web/lib/django/test/simple.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 import sys
       
     2 import signal
       
     3 import unittest
       
     4 
       
     5 from django.conf import settings
       
     6 from django.db.models import get_app, get_apps
       
     7 from django.test import _doctest as doctest
       
     8 from django.test.utils import setup_test_environment, teardown_test_environment
       
     9 from django.test.testcases import OutputChecker, DocTestRunner, TestCase
       
    10 
       
    11 # The module name for tests outside models.py
       
    12 TEST_MODULE = 'tests'
       
    13 
       
    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
       
    63 
       
    64 def get_tests(app_module):
       
    65     try:
       
    66         app_path = app_module.__name__.split('.')[:-1]
       
    67         test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE)
       
    68     except ImportError, e:
       
    69         # Couldn't import tests.py. Was it due to a missing file, or
       
    70         # due to an import error in a tests.py that actually exists?
       
    71         import os.path
       
    72         from imp import find_module
       
    73         try:
       
    74             mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)])
       
    75         except ImportError:
       
    76             # 'tests' module doesn't exist. Move on.
       
    77             test_module = None
       
    78         else:
       
    79             # The module exists, so there must be an import error in the
       
    80             # test module itself. We don't need the module; so if the
       
    81             # module was a single file module (i.e., tests.py), close the file
       
    82             # handle returned by find_module. Otherwise, the test module
       
    83             # is a directory, and there is nothing to close.
       
    84             if mod[0]:
       
    85                 mod[0].close()
       
    86             raise
       
    87     return test_module
       
    88 
       
    89 def build_suite(app_module):
       
    90     "Create a complete Django test suite for the provided application module"
       
    91     suite = unittest.TestSuite()
       
    92 
       
    93     # Load unit and doctests in the models.py module. If module has
       
    94     # a suite() method, use it. Otherwise build the test suite ourselves.
       
    95     if hasattr(app_module, 'suite'):
       
    96         suite.addTest(app_module.suite())
       
    97     else:
       
    98         suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(app_module))
       
    99         try:
       
   100             suite.addTest(doctest.DocTestSuite(app_module,
       
   101                                                checker=doctestOutputChecker,
       
   102                                                runner=DocTestRunner))
       
   103         except ValueError:
       
   104             # No doc tests in models.py
       
   105             pass
       
   106 
       
   107     # Check to see if a separate 'tests' module exists parallel to the
       
   108     # models module
       
   109     test_module = get_tests(app_module)
       
   110     if test_module:
       
   111         # Load unit and doctests in the tests.py module. If module has
       
   112         # a suite() method, use it. Otherwise build the test suite ourselves.
       
   113         if hasattr(test_module, 'suite'):
       
   114             suite.addTest(test_module.suite())
       
   115         else:
       
   116             suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module))
       
   117             try:
       
   118                 suite.addTest(doctest.DocTestSuite(test_module,
       
   119                                                    checker=doctestOutputChecker,
       
   120                                                    runner=DocTestRunner))
       
   121             except ValueError:
       
   122                 # No doc tests in tests.py
       
   123                 pass
       
   124     return suite
       
   125 
       
   126 def build_test(label):
       
   127     """Construct a test case with the specified label. Label should be of the
       
   128     form model.TestClass or model.TestClass.test_method. Returns an
       
   129     instantiated test or test suite corresponding to the label provided.
       
   130 
       
   131     """
       
   132     parts = label.split('.')
       
   133     if len(parts) < 2 or len(parts) > 3:
       
   134         raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label)
       
   135 
       
   136     #
       
   137     # First, look for TestCase instances with a name that matches
       
   138     #
       
   139     app_module = get_app(parts[0])
       
   140     test_module = get_tests(app_module)
       
   141     TestClass = getattr(app_module, parts[1], None)
       
   142 
       
   143     # Couldn't find the test class in models.py; look in tests.py
       
   144     if TestClass is None:
       
   145         if test_module:
       
   146             TestClass = getattr(test_module, parts[1], None)
       
   147 
       
   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:
       
   166         try:
       
   167             doctests = doctest.DocTestSuite(module,
       
   168                                             checker=doctestOutputChecker,
       
   169                                             runner=DocTestRunner)
       
   170             # Now iterate over the suite, looking for doctests whose name
       
   171             # matches the pattern that was given
       
   172             for test in doctests:
       
   173                 if test._dt_test.name in (
       
   174                         '%s.%s' % (module.__name__, '.'.join(parts[1:])),
       
   175                         '%s.__test__.%s' % (module.__name__, '.'.join(parts[1:]))):
       
   176                     tests.append(test)
       
   177         except ValueError:
       
   178             # No doctests found.
       
   179             pass
       
   180 
       
   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)
       
   187 
       
   188 def partition_suite(suite, classes, bins):
       
   189     """
       
   190     Partitions a test suite by test type.
       
   191 
       
   192     classes is a sequence of types
       
   193     bins is a sequence of TestSuites, one more than classes
       
   194 
       
   195     Tests of type classes[i] are added to bins[i],
       
   196     tests with no match found in classes are place in bins[-1]
       
   197     """
       
   198     for test in suite:
       
   199         if isinstance(test, unittest.TestSuite):
       
   200             partition_suite(test, classes, bins)
       
   201         else:
       
   202             for i in range(len(classes)):
       
   203                 if isinstance(test, classes[i]):
       
   204                     bins[i].addTest(test)
       
   205                     break
       
   206             else:
       
   207                 bins[-1].addTest(test)
       
   208 
       
   209 def reorder_suite(suite, classes):
       
   210     """
       
   211     Reorders a test suite by test type.
       
   212 
       
   213     classes is a sequence of types
       
   214 
       
   215     All tests of type clases[0] are placed first, then tests of type classes[1], etc.
       
   216     Tests with no match in classes are placed last.
       
   217     """
       
   218     class_count = len(classes)
       
   219     bins = [unittest.TestSuite() for i in range(class_count+1)]
       
   220     partition_suite(suite, classes, bins)
       
   221     for i in range(class_count):
       
   222         bins[0].addTests(bins[i+1])
       
   223     return bins[0]
       
   224 
       
   225 
       
   226 class DjangoTestSuiteRunner(object):
       
   227     def __init__(self, verbosity=1, interactive=True, failfast=True, **kwargs):
       
   228         self.verbosity = verbosity
       
   229         self.interactive = interactive
       
   230         self.failfast = failfast
       
   231 
       
   232     def setup_test_environment(self, **kwargs):
       
   233         setup_test_environment()
       
   234         settings.DEBUG = False
       
   235 
       
   236     def build_suite(self, test_labels, extra_tests=None, **kwargs):
       
   237         suite = unittest.TestSuite()
       
   238 
       
   239         if test_labels:
       
   240             for label in test_labels:
       
   241                 if '.' in label:
       
   242                     suite.addTest(build_test(label))
       
   243                 else:
       
   244                     app = get_app(label)
       
   245                     suite.addTest(build_suite(app))
       
   246         else:
       
   247             for app in get_apps():
       
   248                 suite.addTest(build_suite(app))
       
   249 
       
   250         if extra_tests:
       
   251             for test in extra_tests:
       
   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]
       
   268             else:
       
   269                 old_names.append((connection, connection.settings_dict['NAME']))
       
   270                 connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive)
       
   271         return old_names, mirrors
       
   272 
       
   273     def run_suite(self, suite, **kwargs):
       
   274         return DjangoTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite)
       
   275 
       
   276     def teardown_databases(self, old_config, **kwargs):
       
   277         from django.db import connections
       
   278         old_names, mirrors = old_config
       
   279         # Point all the mirrors back to the originals
       
   280         for alias, connection in mirrors:
       
   281             connections._connections[alias] = connection
       
   282         # Destroy all the non-mirror databases
       
   283         for connection, old_name in old_names:
       
   284             connection.creation.destroy_test_db(old_name, self.verbosity)
       
   285 
       
   286     def teardown_test_environment(self, **kwargs):
       
   287         teardown_test_environment()
       
   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)