diff -r 8d941af65caf -r 77b6da96e6f1 web/lib/django/test/simple.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/django/test/simple.py Wed Jun 02 18:57:35 2010 +0200 @@ -0,0 +1,326 @@ +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 +from django.test.utils import setup_test_environment, teardown_test_environment +from django.test.testcases import OutputChecker, DocTestRunner, TestCase + +# The module name for tests outside models.py +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) + try: + result = super(DjangoTestRunner, self).run(*args, **kwargs) + finally: + 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 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 + +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] + + +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 + + 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 + old_names = [] + mirrors = [] + for alias in connections: + connection = connections[alias] + # If the database is a test mirror, redirect it's connection + # instead of creating a test database. + if connection.settings_dict['TEST_MIRROR']: + mirrors.append((alias, connection)) + mirror_alias = connection.settings_dict['TEST_MIRROR'] + connections._connections[alias] = connections[mirror_alias] + else: + old_names.append((connection, connection.settings_dict['NAME'])) + connection.creation.create_test_db(self.verbosity, autoclobber=not self.interactive) + return old_names, mirrors + + def run_suite(self, suite, **kwargs): + return DjangoTestRunner(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, connection in mirrors: + connections._connections[alias] = connection + # Destroy all the non-mirror databases + for connection, old_name in old_names: + connection.creation.destroy_test_db(old_name, self.verbosity) + + def teardown_test_environment(self, **kwargs): + 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.', + PendingDeprecationWarning + ) + test_runner = DjangoTestSuiteRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) + return test_runner.run_tests(test_labels, extra_tests=extra_tests)