From 916aecd29d5e3c5e02b620fbd38eac6d61ce6a5d Mon Sep 17 00:00:00 2001 From: Jayden Kneller Date: Thu, 18 Oct 2018 17:04:28 -0600 Subject: [PATCH] Fixed #29866 -- Made DiscoverRunner do tests tear down if running checks or tests raises an exception. --- django/test/runner.py | 20 ++++++++++--- tests/test_runner/tests.py | 57 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/django/test/runner.py b/django/test/runner.py index 38eb5c3be0..ed969cc42f 100644 --- a/django/test/runner.py +++ b/django/test/runner.py @@ -602,10 +602,22 @@ class DiscoverRunner: self.setup_test_environment() suite = self.build_suite(test_labels, extra_tests) old_config = self.setup_databases() - self.run_checks() - result = self.run_suite(suite) - self.teardown_databases(old_config) - self.teardown_test_environment() + run_failed = False + try: + self.run_checks() + result = self.run_suite(suite) + except Exception: + run_failed = True + raise + finally: + try: + self.teardown_databases(old_config) + self.teardown_test_environment() + except Exception: + # Silence teardown exceptions if an exception was raised during + # runs to avoid shadowing it. + if not run_failed: + raise return self.suite_result(suite, result) diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py index 9717d329c1..b496d79369 100644 --- a/tests/test_runner/tests.py +++ b/tests/test_runner/tests.py @@ -10,6 +10,7 @@ from django import db from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.management import call_command +from django.core.management.base import SystemCheckError from django.test import ( TestCase, TransactionTestCase, skipUnlessDBFeature, testcases, ) @@ -407,3 +408,59 @@ class EmptyDefaultDatabaseTest(unittest.TestCase): connection = testcases.connections[db.utils.DEFAULT_DB_ALIAS] self.assertEqual(connection.settings_dict['ENGINE'], 'django.db.backends.dummy') connections_support_transactions() + + +class RunTestsExceptionHandlingTests(unittest.TestCase): + def test_run_checks_raises(self): + """ + Teardown functions are run when run_checks() raises SystemCheckError. + """ + with mock.patch('django.test.runner.DiscoverRunner.setup_test_environment'), \ + mock.patch('django.test.runner.DiscoverRunner.setup_databases'), \ + mock.patch('django.test.runner.DiscoverRunner.build_suite'), \ + mock.patch('django.test.runner.DiscoverRunner.run_checks', side_effect=SystemCheckError), \ + mock.patch('django.test.runner.DiscoverRunner.teardown_databases') as teardown_databases, \ + mock.patch('django.test.runner.DiscoverRunner.teardown_test_environment') as teardown_test_environment: + runner = DiscoverRunner(verbosity=0, interactive=False) + with self.assertRaises(SystemCheckError): + runner.run_tests(['test_runner_apps.sample.tests_sample.TestDjangoTestCase']) + self.assertTrue(teardown_databases.called) + self.assertTrue(teardown_test_environment.called) + + def test_run_checks_raises_and_teardown_raises(self): + """ + SystemCheckError is surfaced when run_checks() raises SystemCheckError + and teardown databases() raises ValueError. + """ + with mock.patch('django.test.runner.DiscoverRunner.setup_test_environment'), \ + mock.patch('django.test.runner.DiscoverRunner.setup_databases'), \ + mock.patch('django.test.runner.DiscoverRunner.build_suite'), \ + mock.patch('django.test.runner.DiscoverRunner.run_checks', side_effect=SystemCheckError), \ + mock.patch('django.test.runner.DiscoverRunner.teardown_databases', side_effect=ValueError) \ + as teardown_databases, \ + mock.patch('django.test.runner.DiscoverRunner.teardown_test_environment') as teardown_test_environment: + runner = DiscoverRunner(verbosity=0, interactive=False) + with self.assertRaises(SystemCheckError): + runner.run_tests(['test_runner_apps.sample.tests_sample.TestDjangoTestCase']) + self.assertTrue(teardown_databases.called) + self.assertFalse(teardown_test_environment.called) + + def test_run_checks_passes_and_teardown_raises(self): + """ + Exceptions on teardown are surfaced if no exceptions happen during + run_checks(). + """ + with mock.patch('django.test.runner.DiscoverRunner.setup_test_environment'), \ + mock.patch('django.test.runner.DiscoverRunner.setup_databases'), \ + mock.patch('django.test.runner.DiscoverRunner.build_suite'), \ + mock.patch('django.test.runner.DiscoverRunner.run_checks'), \ + mock.patch('django.test.runner.DiscoverRunner.teardown_databases', side_effect=ValueError) \ + as teardown_databases, \ + mock.patch('django.test.runner.DiscoverRunner.teardown_test_environment') as teardown_test_environment: + runner = DiscoverRunner(verbosity=0, interactive=False) + with self.assertRaises(ValueError): + # Suppress the output when running TestDjangoTestCase. + with mock.patch('sys.stderr'): + runner.run_tests(['test_runner_apps.sample.tests_sample.TestDjangoTestCase']) + self.assertTrue(teardown_databases.called) + self.assertFalse(teardown_test_environment.called)