| import unittest as real_unittest | |
| 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, real_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, (db_name, 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, (db_name, aliases))) | |
| changed = True | |
| else: | |
| deferred.append((signature, (db_name, 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. | |
| item = test_databases.setdefault( | |
| connection.creation.test_db_signature(), | |
| (connection.settings_dict['NAME'], []) | |
| ) | |
| item[1].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 signature, (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) |