| import sys |
| import signal |
| |
| from django.conf import settings |
| from django.core.exceptions import ImproperlyConfigured |
| from django.db.models import get_app, get_apps |
| from django.test import _doctest as doctest |
| from django.test.utils import setup_test_environment, teardown_test_environment |
| from django.test.testcases import OutputChecker, DocTestRunner, TestCase |
| from django.utils import unittest |
| |
| try: |
| all |
| except NameError: |
| from django.utils.itercompat import all |
| |
| |
| __all__ = ('DjangoTestRunner', 'DjangoTestSuiteRunner', 'run_tests') |
| |
| # The module name for tests outside models.py |
| TEST_MODULE = 'tests' |
| |
| doctestOutputChecker = OutputChecker() |
| |
| class DjangoTestRunner(unittest.TextTestRunner): |
| def __init__(self, *args, **kwargs): |
| import warnings |
| warnings.warn( |
| "DjangoTestRunner is deprecated; it's functionality is indistinguishable from TextTestRunner", |
| PendingDeprecationWarning |
| ) |
| super(DjangoTestRunner, self).__init__(*args, **kwargs) |
| |
| def get_tests(app_module): |
| try: |
| app_path = app_module.__name__.split('.')[:-1] |
| test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE) |
| except ImportError, e: |
| # Couldn't import tests.py. Was it due to a missing file, or |
| # due to an import error in a tests.py that actually exists? |
| import os.path |
| from imp import find_module |
| try: |
| mod = find_module(TEST_MODULE, [os.path.dirname(app_module.__file__)]) |
| except ImportError: |
| # 'tests' module doesn't exist. Move on. |
| test_module = None |
| else: |
| # The module exists, so there must be an import error in the |
| # test module itself. We don't need the module; so if the |
| # module was a single file module (i.e., tests.py), close the file |
| # handle returned by find_module. Otherwise, the test module |
| # is a directory, and there is nothing to close. |
| if mod[0]: |
| mod[0].close() |
| raise |
| return test_module |
| |
| def build_suite(app_module): |
| "Create a complete Django test suite for the provided application module" |
| suite = unittest.TestSuite() |
| |
| # Load unit and doctests in the models.py module. If module has |
| # a suite() method, use it. Otherwise build the test suite ourselves. |
| if hasattr(app_module, 'suite'): |
| suite.addTest(app_module.suite()) |
| else: |
| suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(app_module)) |
| try: |
| suite.addTest(doctest.DocTestSuite(app_module, |
| checker=doctestOutputChecker, |
| runner=DocTestRunner)) |
| except ValueError: |
| # No doc tests in models.py |
| pass |
| |
| # Check to see if a separate 'tests' module exists parallel to the |
| # models module |
| test_module = get_tests(app_module) |
| if test_module: |
| # Load unit and doctests in the tests.py module. If module has |
| # a suite() method, use it. Otherwise build the test suite ourselves. |
| if hasattr(test_module, 'suite'): |
| suite.addTest(test_module.suite()) |
| else: |
| suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_module)) |
| try: |
| suite.addTest(doctest.DocTestSuite(test_module, |
| checker=doctestOutputChecker, |
| runner=DocTestRunner)) |
| except ValueError: |
| # No doc tests in tests.py |
| pass |
| return suite |
| |
| def build_test(label): |
| """Construct a test case with the specified label. Label should be of the |
| form model.TestClass or model.TestClass.test_method. Returns an |
| instantiated test or test suite corresponding to the label provided. |
| |
| """ |
| parts = label.split('.') |
| if len(parts) < 2 or len(parts) > 3: |
| raise ValueError("Test label '%s' should be of the form app.TestCase or app.TestCase.test_method" % label) |
| |
| # |
| # First, look for TestCase instances with a name that matches |
| # |
| app_module = get_app(parts[0]) |
| test_module = get_tests(app_module) |
| TestClass = getattr(app_module, parts[1], None) |
| |
| # Couldn't find the test class in models.py; look in tests.py |
| if TestClass is None: |
| if test_module: |
| TestClass = getattr(test_module, parts[1], None) |
| |
| try: |
| if issubclass(TestClass, unittest.TestCase): |
| if len(parts) == 2: # label is app.TestClass |
| try: |
| return unittest.TestLoader().loadTestsFromTestCase(TestClass) |
| except TypeError: |
| raise ValueError("Test label '%s' does not refer to a test class" % label) |
| else: # label is app.TestClass.test_method |
| return TestClass(parts[2]) |
| except TypeError: |
| # TestClass isn't a TestClass - it must be a method or normal class |
| pass |
| |
| # |
| # If there isn't a TestCase, look for a doctest that matches |
| # |
| tests = [] |
| for module in app_module, test_module: |
| try: |
| doctests = doctest.DocTestSuite(module, |
| checker=doctestOutputChecker, |
| runner=DocTestRunner) |
| # Now iterate over the suite, looking for doctests whose name |
| # matches the pattern that was given |
| for test in doctests: |
| if test._dt_test.name in ( |
| '%s.%s' % (module.__name__, '.'.join(parts[1:])), |
| '%s.__test__.%s' % (module.__name__, '.'.join(parts[1:]))): |
| tests.append(test) |
| except ValueError: |
| # No doctests found. |
| pass |
| |
| # If no tests were found, then we were given a bad test label. |
| if not tests: |
| raise ValueError("Test label '%s' does not refer to a test" % label) |
| |
| # Construct a suite out of the tests that matched. |
| return unittest.TestSuite(tests) |
| |
| def partition_suite(suite, classes, bins): |
| """ |
| Partitions a test suite by test type. |
| |
| classes is a sequence of types |
| bins is a sequence of TestSuites, one more than classes |
| |
| Tests of type classes[i] are added to bins[i], |
| tests with no match found in classes are place in bins[-1] |
| """ |
| for test in suite: |
| if isinstance(test, unittest.TestSuite): |
| partition_suite(test, classes, bins) |
| else: |
| for i in range(len(classes)): |
| if isinstance(test, classes[i]): |
| bins[i].addTest(test) |
| break |
| else: |
| bins[-1].addTest(test) |
| |
| def reorder_suite(suite, classes): |
| """ |
| Reorders a test suite by test type. |
| |
| classes is a sequence of types |
| |
| All tests of type clases[0] are placed first, then tests of type classes[1], etc. |
| Tests with no match in classes are placed last. |
| """ |
| class_count = len(classes) |
| bins = [unittest.TestSuite() for i in range(class_count+1)] |
| partition_suite(suite, classes, bins) |
| for i in range(class_count): |
| bins[0].addTests(bins[i+1]) |
| return bins[0] |
| |
| def dependency_ordered(test_databases, dependencies): |
| """Reorder test_databases into an order that honors the dependencies |
| described in TEST_DEPENDENCIES. |
| """ |
| ordered_test_databases = [] |
| resolved_databases = set() |
| while test_databases: |
| changed = False |
| deferred = [] |
| |
| while test_databases: |
| signature, aliases = test_databases.pop() |
| dependencies_satisfied = True |
| for alias in aliases: |
| if alias in dependencies: |
| if all(a in resolved_databases for a in dependencies[alias]): |
| # all dependencies for this alias are satisfied |
| dependencies.pop(alias) |
| resolved_databases.add(alias) |
| else: |
| dependencies_satisfied = False |
| else: |
| resolved_databases.add(alias) |
| |
| if dependencies_satisfied: |
| ordered_test_databases.append((signature, aliases)) |
| changed = True |
| else: |
| deferred.append((signature, aliases)) |
| |
| if not changed: |
| raise ImproperlyConfigured("Circular dependency in TEST_DEPENDENCIES") |
| test_databases = deferred |
| return ordered_test_databases |
| |
| class DjangoTestSuiteRunner(object): |
| def __init__(self, verbosity=1, interactive=True, failfast=True, **kwargs): |
| self.verbosity = verbosity |
| self.interactive = interactive |
| self.failfast = failfast |
| |
| def setup_test_environment(self, **kwargs): |
| setup_test_environment() |
| settings.DEBUG = False |
| unittest.installHandler() |
| |
| def build_suite(self, test_labels, extra_tests=None, **kwargs): |
| suite = unittest.TestSuite() |
| |
| 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)) |
| |
| if extra_tests: |
| for test in extra_tests: |
| suite.addTest(test) |
| |
| return reorder_suite(suite, (TestCase,)) |
| |
| def setup_databases(self, **kwargs): |
| from django.db import connections, DEFAULT_DB_ALIAS |
| |
| # First pass -- work out which databases actually need to be created, |
| # and which ones are test mirrors or duplicate entries in DATABASES |
| mirrored_aliases = {} |
| test_databases = {} |
| dependencies = {} |
| for alias in connections: |
| connection = connections[alias] |
| if connection.settings_dict['TEST_MIRROR']: |
| # If the database is marked as a test mirror, save |
| # the alias. |
| mirrored_aliases[alias] = connection.settings_dict['TEST_MIRROR'] |
| else: |
| # Store a tuple with DB parameters that uniquely identify it. |
| # If we have two aliases with the same values for that tuple, |
| # we only need to create the test database once. |
| test_databases.setdefault(( |
| connection.settings_dict['HOST'], |
| connection.settings_dict['PORT'], |
| connection.settings_dict['ENGINE'], |
| connection.settings_dict['NAME'], |
| ), []).append(alias) |
| |
| if 'TEST_DEPENDENCIES' in connection.settings_dict: |
| dependencies[alias] = connection.settings_dict['TEST_DEPENDENCIES'] |
| else: |
| if alias != DEFAULT_DB_ALIAS: |
| dependencies[alias] = connection.settings_dict.get('TEST_DEPENDENCIES', [DEFAULT_DB_ALIAS]) |
| |
| # Second pass -- actually create the databases. |
| old_names = [] |
| mirrors = [] |
| for (host, port, engine, db_name), aliases in dependency_ordered(test_databases.items(), dependencies): |
| # Actually create the database for the first connection |
| connection = connections[aliases[0]] |
| old_names.append((connection, db_name, True)) |
| test_db_name = connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive) |
| for alias in aliases[1:]: |
| connection = connections[alias] |
| if db_name: |
| old_names.append((connection, db_name, False)) |
| connection.settings_dict['NAME'] = test_db_name |
| else: |
| # If settings_dict['NAME'] isn't defined, we have a backend where |
| # the name isn't important -- e.g., SQLite, which uses :memory:. |
| # Force create the database instead of assuming it's a duplicate. |
| old_names.append((connection, db_name, True)) |
| connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive) |
| |
| for alias, mirror_alias in mirrored_aliases.items(): |
| mirrors.append((alias, connections[alias].settings_dict['NAME'])) |
| connections[alias].settings_dict['NAME'] = connections[mirror_alias].settings_dict['NAME'] |
| |
| return old_names, mirrors |
| |
| def run_suite(self, suite, **kwargs): |
| return unittest.TextTestRunner(verbosity=self.verbosity, failfast=self.failfast).run(suite) |
| |
| def teardown_databases(self, old_config, **kwargs): |
| from django.db import connections |
| old_names, mirrors = old_config |
| # Point all the mirrors back to the originals |
| for alias, old_name in mirrors: |
| connections[alias].settings_dict['NAME'] = old_name |
| # Destroy all the non-mirror databases |
| for connection, old_name, destroy in old_names: |
| if destroy: |
| connection.creation.destroy_test_db(old_name, self.verbosity) |
| else: |
| connection.settings_dict['NAME'] = old_name |
| |
| def teardown_test_environment(self, **kwargs): |
| unittest.removeHandler() |
| teardown_test_environment() |
| |
| def suite_result(self, suite, result, **kwargs): |
| return len(result.failures) + len(result.errors) |
| |
| def run_tests(self, test_labels, extra_tests=None, **kwargs): |
| """ |
| 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() |
| suite = self.build_suite(test_labels, extra_tests) |
| old_config = self.setup_databases() |
| result = self.run_suite(suite) |
| self.teardown_databases(old_config) |
| self.teardown_test_environment() |
| return self.suite_result(suite, 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.', |
| DeprecationWarning |
| ) |
| test_runner = DjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) |
| return test_runner.run_tests(test_labels, extra_tests=extra_tests) |