Fixed #24652 -- Disallowed query execution in SimpleTestCase subclasses.

Thanks to Tim and Anssi for the review.
This commit is contained in:
Simon Charette 2015-04-16 16:19:30 -04:00
parent ead36e8a47
commit c15b0c2792
4 changed files with 64 additions and 0 deletions

View File

@ -140,6 +140,19 @@ class _AssertTemplateNotUsedContext(_AssertTemplateUsedContext):
return '%s was rendered.' % self.template_name return '%s was rendered.' % self.template_name
class _CursorFailure(object):
def __init__(self, cls_name, wrapped):
self.cls_name = cls_name
self.wrapped = wrapped
def __call__(self):
raise AssertionError(
"Database queries aren't allowed in SimpleTestCase. "
"Either use TestCase or TransactionTestCase to ensure proper test isolation or "
"set %s.allow_database_queries to True to silence this failure." % self.cls_name
)
class SimpleTestCase(unittest.TestCase): class SimpleTestCase(unittest.TestCase):
# The class we'll use for the test client self.client. # The class we'll use for the test client self.client.
@ -148,6 +161,10 @@ class SimpleTestCase(unittest.TestCase):
_overridden_settings = None _overridden_settings = None
_modified_settings = None _modified_settings = None
# Tests shouldn't be allowed to query the database since
# this base class doesn't enforce any isolation.
allow_database_queries = False
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super(SimpleTestCase, cls).setUpClass() super(SimpleTestCase, cls).setUpClass()
@ -157,9 +174,17 @@ class SimpleTestCase(unittest.TestCase):
if cls._modified_settings: if cls._modified_settings:
cls._cls_modified_context = modify_settings(cls._modified_settings) cls._cls_modified_context = modify_settings(cls._modified_settings)
cls._cls_modified_context.enable() cls._cls_modified_context.enable()
if not cls.allow_database_queries:
for alias in connections:
connection = connections[alias]
connection.cursor = _CursorFailure(cls.__name__, connection.cursor)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
if not cls.allow_database_queries:
for alias in connections:
connection = connections[alias]
connection.cursor = connection.cursor.wrapped
if hasattr(cls, '_cls_modified_context'): if hasattr(cls, '_cls_modified_context'):
cls._cls_modified_context.disable() cls._cls_modified_context.disable()
delattr(cls, '_cls_modified_context') delattr(cls, '_cls_modified_context')
@ -777,6 +802,10 @@ class TransactionTestCase(SimpleTestCase):
# This can be slow; this flag allows enabling on a per-case basis. # This can be slow; this flag allows enabling on a per-case basis.
serialized_rollback = False serialized_rollback = False
# Since tests will be wrapped in a transaction, or serialized if they
# are not available, we allow queries to be run.
allow_database_queries = True
def _pre_setup(self): def _pre_setup(self):
"""Performs any pre-test setup. This includes: """Performs any pre-test setup. This includes:

View File

@ -505,6 +505,12 @@ Miscellaneous
* The ``django.contrib.sites.models.Site.domain`` field was changed to be * The ``django.contrib.sites.models.Site.domain`` field was changed to be
:attr:`~django.db.models.Field.unique`. :attr:`~django.db.models.Field.unique`.
* In order to enforce test isolation, database queries are not allowed
by default in :class:`~django.test.SimpleTestCase` tests anymore. You
can disable this behavior by setting the
:attr:`~django.test.SimpleTestCase.allow_database_queries` class attribute
to ``True`` on your test class.
.. _deprecated-features-1.9: .. _deprecated-features-1.9:
Features deprecated in 1.9 Features deprecated in 1.9

View File

@ -606,6 +606,17 @@ features like:
then you should use :class:`~django.test.TransactionTestCase` or then you should use :class:`~django.test.TransactionTestCase` or
:class:`~django.test.TestCase` instead. :class:`~django.test.TestCase` instead.
.. attribute:: SimpleTestCase.allow_database_queries
.. versionadded:: 1.9
:class:`~SimpleTestCase` disallows database queries by default. This
helps to avoid executing write queries which will affect other tests
since each ``SimpleTestCase`` test isn't run in a transaction. If you
aren't concerned about this problem, you can disable this behavior by
setting the ``allow_database_queries`` class attribute to ``True`` on
your test class.
``SimpleTestCase`` inherits from ``unittest.TestCase``. ``SimpleTestCase`` inherits from ``unittest.TestCase``.
.. warning:: .. warning::

View File

@ -914,3 +914,21 @@ class OverrideSettingsTests(TestCase):
with self.settings(STATICFILES_DIRS=[test_path]): with self.settings(STATICFILES_DIRS=[test_path]):
finder = get_finder('django.contrib.staticfiles.finders.FileSystemFinder') finder = get_finder('django.contrib.staticfiles.finders.FileSystemFinder')
self.assertIn(expected_location, finder.locations) self.assertIn(expected_location, finder.locations)
class DisallowedDatabaseQueriesTests(SimpleTestCase):
def test_disallowed_database_queries(self):
expected_message = (
"Database queries aren't allowed in SimpleTestCase. "
"Either use TestCase or TransactionTestCase to ensure proper test isolation or "
"set DisallowedDatabaseQueriesTests.allow_database_queries to True to silence this failure."
)
with self.assertRaisesMessage(AssertionError, expected_message):
Car.objects.first()
class AllowedDatabaseQueriesTests(SimpleTestCase):
allow_database_queries = True
def test_allowed_database_queries(self):
Car.objects.first()