mirror of
https://github.com/django/django.git
synced 2025-04-25 09:44:36 +00:00
Refs #28478 -- Prevented database feature based skipping on tests disallowing queries.
Database features may require a connection to be established to determine whether or not they are enabled.
This commit is contained in:
parent
f5b635086a
commit
b181aba7dd
@ -1205,12 +1205,24 @@ class CheckCondition:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _deferredSkip(condition, reason):
|
def _deferredSkip(condition, reason, name):
|
||||||
def decorator(test_func):
|
def decorator(test_func):
|
||||||
|
nonlocal condition
|
||||||
if not (isinstance(test_func, type) and
|
if not (isinstance(test_func, type) and
|
||||||
issubclass(test_func, unittest.TestCase)):
|
issubclass(test_func, unittest.TestCase)):
|
||||||
@wraps(test_func)
|
@wraps(test_func)
|
||||||
def skip_wrapper(*args, **kwargs):
|
def skip_wrapper(*args, **kwargs):
|
||||||
|
if (args and isinstance(args[0], unittest.TestCase) and
|
||||||
|
connection.alias not in getattr(args[0], 'databases', {})):
|
||||||
|
raise ValueError(
|
||||||
|
"%s cannot be used on %s as %s doesn't allow queries "
|
||||||
|
"against the %r database." % (
|
||||||
|
name,
|
||||||
|
args[0],
|
||||||
|
args[0].__class__.__qualname__,
|
||||||
|
connection.alias,
|
||||||
|
)
|
||||||
|
)
|
||||||
if condition():
|
if condition():
|
||||||
raise unittest.SkipTest(reason)
|
raise unittest.SkipTest(reason)
|
||||||
return test_func(*args, **kwargs)
|
return test_func(*args, **kwargs)
|
||||||
@ -1218,6 +1230,16 @@ def _deferredSkip(condition, reason):
|
|||||||
else:
|
else:
|
||||||
# Assume a class is decorated
|
# Assume a class is decorated
|
||||||
test_item = test_func
|
test_item = test_func
|
||||||
|
databases = getattr(test_item, 'databases', None)
|
||||||
|
if not databases or connection.alias not in databases:
|
||||||
|
# Defer raising to allow importing test class's module.
|
||||||
|
def condition():
|
||||||
|
raise ValueError(
|
||||||
|
"%s cannot be used on %s as it doesn't allow queries "
|
||||||
|
"against the '%s' database." % (
|
||||||
|
name, test_item, connection.alias,
|
||||||
|
)
|
||||||
|
)
|
||||||
# Retrieve the possibly existing value from the class's dict to
|
# Retrieve the possibly existing value from the class's dict to
|
||||||
# avoid triggering the descriptor.
|
# avoid triggering the descriptor.
|
||||||
skip = test_func.__dict__.get('__unittest_skip__')
|
skip = test_func.__dict__.get('__unittest_skip__')
|
||||||
@ -1233,7 +1255,8 @@ def skipIfDBFeature(*features):
|
|||||||
"""Skip a test if a database has at least one of the named features."""
|
"""Skip a test if a database has at least one of the named features."""
|
||||||
return _deferredSkip(
|
return _deferredSkip(
|
||||||
lambda: any(getattr(connection.features, feature, False) for feature in features),
|
lambda: any(getattr(connection.features, feature, False) for feature in features),
|
||||||
"Database has feature(s) %s" % ", ".join(features)
|
"Database has feature(s) %s" % ", ".join(features),
|
||||||
|
'skipIfDBFeature',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1241,7 +1264,8 @@ def skipUnlessDBFeature(*features):
|
|||||||
"""Skip a test unless a database has all the named features."""
|
"""Skip a test unless a database has all the named features."""
|
||||||
return _deferredSkip(
|
return _deferredSkip(
|
||||||
lambda: not all(getattr(connection.features, feature, False) for feature in features),
|
lambda: not all(getattr(connection.features, feature, False) for feature in features),
|
||||||
"Database doesn't support feature(s): %s" % ", ".join(features)
|
"Database doesn't support feature(s): %s" % ", ".join(features),
|
||||||
|
'skipUnlessDBFeature',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1249,7 +1273,8 @@ def skipUnlessAnyDBFeature(*features):
|
|||||||
"""Skip a test unless a database has any of the named features."""
|
"""Skip a test unless a database has any of the named features."""
|
||||||
return _deferredSkip(
|
return _deferredSkip(
|
||||||
lambda: not any(getattr(connection.features, feature, False) for feature in features),
|
lambda: not any(getattr(connection.features, feature, False) for feature in features),
|
||||||
"Database doesn't support any of the feature(s): %s" % ", ".join(features)
|
"Database doesn't support any of the feature(s): %s" % ", ".join(features),
|
||||||
|
'skipUnlessAnyDBFeature',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,12 +15,6 @@ class SimpleDatabaseOperationTests(SimpleTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.ops = BaseDatabaseOperations(connection=connection)
|
self.ops = BaseDatabaseOperations(connection=connection)
|
||||||
|
|
||||||
@skipIfDBFeature('can_distinct_on_fields')
|
|
||||||
def test_distinct_on_fields(self):
|
|
||||||
msg = 'DISTINCT ON fields is not supported by this database backend'
|
|
||||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
|
||||||
self.ops.distinct_sql(['a', 'b'], None)
|
|
||||||
|
|
||||||
def test_deferrable_sql(self):
|
def test_deferrable_sql(self):
|
||||||
self.assertEqual(self.ops.deferrable_sql(), '')
|
self.assertEqual(self.ops.deferrable_sql(), '')
|
||||||
|
|
||||||
@ -123,6 +117,23 @@ class SimpleDatabaseOperationTests(SimpleTestCase):
|
|||||||
with self.assertRaisesMessage(NotImplementedError, self.may_requre_msg % 'datetime_extract_sql'):
|
with self.assertRaisesMessage(NotImplementedError, self.may_requre_msg % 'datetime_extract_sql'):
|
||||||
self.ops.datetime_extract_sql(None, None, None)
|
self.ops.datetime_extract_sql(None, None, None)
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseOperationTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.ops = BaseDatabaseOperations(connection=connection)
|
||||||
|
|
||||||
|
@skipIfDBFeature('supports_over_clause')
|
||||||
|
def test_window_frame_raise_not_supported_error(self):
|
||||||
|
msg = 'This backend does not support window expressions.'
|
||||||
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
|
self.ops.window_frame_rows_start_end()
|
||||||
|
|
||||||
|
@skipIfDBFeature('can_distinct_on_fields')
|
||||||
|
def test_distinct_on_fields(self):
|
||||||
|
msg = 'DISTINCT ON fields is not supported by this database backend'
|
||||||
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
|
self.ops.distinct_sql(['a', 'b'], None)
|
||||||
|
|
||||||
@skipIfDBFeature('supports_temporal_subtraction')
|
@skipIfDBFeature('supports_temporal_subtraction')
|
||||||
def test_subtract_temporals(self):
|
def test_subtract_temporals(self):
|
||||||
duration_field = DurationField()
|
duration_field = DurationField()
|
||||||
@ -133,13 +144,3 @@ class SimpleDatabaseOperationTests(SimpleTestCase):
|
|||||||
)
|
)
|
||||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||||
self.ops.subtract_temporals(duration_field_internal_type, None, None)
|
self.ops.subtract_temporals(duration_field_internal_type, None, None)
|
||||||
|
|
||||||
|
|
||||||
class DatabaseOperationTests(TestCase):
|
|
||||||
# Checking the 'supports_over_clause' feature requires a query for the
|
|
||||||
# MySQL backend to perform a version check.
|
|
||||||
@skipIfDBFeature('supports_over_clause')
|
|
||||||
def test_window_frame_raise_not_supported_error(self):
|
|
||||||
msg = 'This backend does not support window expressions.'
|
|
||||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
|
||||||
connection.ops.window_frame_rows_start_end()
|
|
||||||
|
@ -2,7 +2,7 @@ import unittest
|
|||||||
|
|
||||||
from django.core.checks import Error, Warning as DjangoWarning
|
from django.core.checks import Error, Warning as DjangoWarning
|
||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
from django.test import SimpleTestCase, skipIfDBFeature
|
from django.test import SimpleTestCase, TestCase, skipIfDBFeature
|
||||||
from django.test.utils import isolate_apps, override_settings
|
from django.test.utils import isolate_apps, override_settings
|
||||||
from django.utils.functional import lazy
|
from django.utils.functional import lazy
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
@ -680,7 +680,7 @@ class TimeFieldTests(SimpleTestCase):
|
|||||||
|
|
||||||
|
|
||||||
@isolate_apps('invalid_models_tests')
|
@isolate_apps('invalid_models_tests')
|
||||||
class TextFieldTests(SimpleTestCase):
|
class TextFieldTests(TestCase):
|
||||||
|
|
||||||
@skipIfDBFeature('supports_index_on_text_field')
|
@skipIfDBFeature('supports_index_on_text_field')
|
||||||
def test_max_length_warning(self):
|
def test_max_length_warning(self):
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
from django.core.checks import Error, Warning as DjangoWarning
|
from django.core.checks import Error, Warning as DjangoWarning
|
||||||
from django.db import models
|
from django.db import connection, models
|
||||||
from django.db.models.fields.related import ForeignObject
|
from django.db.models.fields.related import ForeignObject
|
||||||
from django.test.testcases import SimpleTestCase, skipIfDBFeature
|
from django.test.testcases import SimpleTestCase
|
||||||
from django.test.utils import isolate_apps, override_settings
|
from django.test.utils import isolate_apps, override_settings
|
||||||
|
|
||||||
|
|
||||||
@ -501,13 +503,14 @@ class RelativeFieldTests(SimpleTestCase):
|
|||||||
),
|
),
|
||||||
])
|
])
|
||||||
|
|
||||||
@skipIfDBFeature('interprets_empty_strings_as_nulls')
|
|
||||||
def test_nullable_primary_key(self):
|
def test_nullable_primary_key(self):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
field = models.IntegerField(primary_key=True, null=True)
|
field = models.IntegerField(primary_key=True, null=True)
|
||||||
|
|
||||||
field = Model._meta.get_field('field')
|
field = Model._meta.get_field('field')
|
||||||
self.assertEqual(field.check(), [
|
with mock.patch.object(connection.features, 'interprets_empty_strings_as_nulls', False):
|
||||||
|
results = field.check()
|
||||||
|
self.assertEqual(results, [
|
||||||
Error(
|
Error(
|
||||||
'Primary keys must not have null=True.',
|
'Primary keys must not have null=True.',
|
||||||
hint='Set null=False on the field, or remove primary_key=True argument.',
|
hint='Set null=False on the field, or remove primary_key=True argument.',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
from django.db.models.query_utils import Q
|
from django.db.models.query_utils import Q
|
||||||
from django.test import SimpleTestCase, skipUnlessDBFeature
|
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
from django.test.utils import isolate_apps
|
from django.test.utils import isolate_apps
|
||||||
|
|
||||||
from .models import Book, ChildModel1, ChildModel2
|
from .models import Book, ChildModel1, ChildModel2
|
||||||
|
|
||||||
|
|
||||||
class IndexesTests(SimpleTestCase):
|
class SimpleIndexesTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_suffix(self):
|
def test_suffix(self):
|
||||||
self.assertEqual(models.Index.suffix, 'idx')
|
self.assertEqual(models.Index.suffix, 'idx')
|
||||||
@ -156,6 +156,9 @@ class IndexesTests(SimpleTestCase):
|
|||||||
index_names = [index.name for index in ChildModel2._meta.indexes]
|
index_names = [index.name for index in ChildModel2._meta.indexes]
|
||||||
self.assertEqual(index_names, ['model_index_name_b6c374_idx'])
|
self.assertEqual(index_names, ['model_index_name_b6c374_idx'])
|
||||||
|
|
||||||
|
|
||||||
|
class IndexesTests(TestCase):
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_tablespaces')
|
@skipUnlessDBFeature('supports_tablespaces')
|
||||||
def test_db_tablespace(self):
|
def test_db_tablespace(self):
|
||||||
editor = connection.schema_editor()
|
editor = connection.schema_editor()
|
||||||
|
@ -29,15 +29,14 @@ from .views import empty_response
|
|||||||
|
|
||||||
|
|
||||||
class SkippingTestCase(SimpleTestCase):
|
class SkippingTestCase(SimpleTestCase):
|
||||||
def _assert_skipping(self, func, expected_exc):
|
def _assert_skipping(self, func, expected_exc, msg=None):
|
||||||
# We cannot simply use assertRaises because a SkipTest exception will go unnoticed
|
|
||||||
try:
|
try:
|
||||||
func()
|
if msg is not None:
|
||||||
except expected_exc:
|
self.assertRaisesMessage(expected_exc, msg, func)
|
||||||
pass
|
else:
|
||||||
except Exception as e:
|
self.assertRaises(expected_exc, func)
|
||||||
self.fail("No %s exception should have been raised for %s." % (
|
except unittest.SkipTest:
|
||||||
e.__class__.__name__, func.__name__))
|
self.fail('%s should not result in a skipped test.' % func.__name__)
|
||||||
|
|
||||||
def test_skip_unless_db_feature(self):
|
def test_skip_unless_db_feature(self):
|
||||||
"""
|
"""
|
||||||
@ -65,6 +64,20 @@ class SkippingTestCase(SimpleTestCase):
|
|||||||
self._assert_skipping(test_func3, ValueError)
|
self._assert_skipping(test_func3, ValueError)
|
||||||
self._assert_skipping(test_func4, unittest.SkipTest)
|
self._assert_skipping(test_func4, unittest.SkipTest)
|
||||||
|
|
||||||
|
class SkipTestCase(SimpleTestCase):
|
||||||
|
@skipUnlessDBFeature('missing')
|
||||||
|
def test_foo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self._assert_skipping(
|
||||||
|
SkipTestCase('test_foo').test_foo,
|
||||||
|
ValueError,
|
||||||
|
"skipUnlessDBFeature cannot be used on test_foo (test_utils.tests."
|
||||||
|
"SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase) "
|
||||||
|
"as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase "
|
||||||
|
"doesn't allow queries against the 'default' database."
|
||||||
|
)
|
||||||
|
|
||||||
def test_skip_if_db_feature(self):
|
def test_skip_if_db_feature(self):
|
||||||
"""
|
"""
|
||||||
Testing the django.test.skipIfDBFeature decorator.
|
Testing the django.test.skipIfDBFeature decorator.
|
||||||
@ -95,17 +108,31 @@ class SkippingTestCase(SimpleTestCase):
|
|||||||
self._assert_skipping(test_func4, unittest.SkipTest)
|
self._assert_skipping(test_func4, unittest.SkipTest)
|
||||||
self._assert_skipping(test_func5, ValueError)
|
self._assert_skipping(test_func5, ValueError)
|
||||||
|
|
||||||
|
class SkipTestCase(SimpleTestCase):
|
||||||
|
@skipIfDBFeature('missing')
|
||||||
|
def test_foo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
class SkippingClassTestCase(SimpleTestCase):
|
self._assert_skipping(
|
||||||
|
SkipTestCase('test_foo').test_foo,
|
||||||
|
ValueError,
|
||||||
|
"skipIfDBFeature cannot be used on test_foo (test_utils.tests."
|
||||||
|
"SkippingTestCase.test_skip_if_db_feature.<locals>.SkipTestCase) "
|
||||||
|
"as SkippingTestCase.test_skip_if_db_feature.<locals>.SkipTestCase "
|
||||||
|
"doesn't allow queries against the 'default' database."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SkippingClassTestCase(TestCase):
|
||||||
def test_skip_class_unless_db_feature(self):
|
def test_skip_class_unless_db_feature(self):
|
||||||
@skipUnlessDBFeature("__class__")
|
@skipUnlessDBFeature("__class__")
|
||||||
class NotSkippedTests(unittest.TestCase):
|
class NotSkippedTests(TestCase):
|
||||||
def test_dummy(self):
|
def test_dummy(self):
|
||||||
return
|
return
|
||||||
|
|
||||||
@skipUnlessDBFeature("missing")
|
@skipUnlessDBFeature("missing")
|
||||||
@skipIfDBFeature("__class__")
|
@skipIfDBFeature("__class__")
|
||||||
class SkippedTests(unittest.TestCase):
|
class SkippedTests(TestCase):
|
||||||
def test_will_be_skipped(self):
|
def test_will_be_skipped(self):
|
||||||
self.fail("We should never arrive here.")
|
self.fail("We should never arrive here.")
|
||||||
|
|
||||||
@ -119,13 +146,34 @@ class SkippingClassTestCase(SimpleTestCase):
|
|||||||
test_suite.addTest(SkippedTests('test_will_be_skipped'))
|
test_suite.addTest(SkippedTests('test_will_be_skipped'))
|
||||||
test_suite.addTest(SkippedTestsSubclass('test_will_be_skipped'))
|
test_suite.addTest(SkippedTestsSubclass('test_will_be_skipped'))
|
||||||
except unittest.SkipTest:
|
except unittest.SkipTest:
|
||||||
self.fail("SkipTest should not be raised at this stage")
|
self.fail('SkipTest should not be raised here.')
|
||||||
result = unittest.TextTestRunner(stream=StringIO()).run(test_suite)
|
result = unittest.TextTestRunner(stream=StringIO()).run(test_suite)
|
||||||
self.assertEqual(result.testsRun, 3)
|
self.assertEqual(result.testsRun, 3)
|
||||||
self.assertEqual(len(result.skipped), 2)
|
self.assertEqual(len(result.skipped), 2)
|
||||||
self.assertEqual(result.skipped[0][1], 'Database has feature(s) __class__')
|
self.assertEqual(result.skipped[0][1], 'Database has feature(s) __class__')
|
||||||
self.assertEqual(result.skipped[1][1], 'Database has feature(s) __class__')
|
self.assertEqual(result.skipped[1][1], 'Database has feature(s) __class__')
|
||||||
|
|
||||||
|
def test_missing_default_databases(self):
|
||||||
|
@skipIfDBFeature('missing')
|
||||||
|
class MissingDatabases(SimpleTestCase):
|
||||||
|
def test_assertion_error(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
suite = unittest.TestSuite()
|
||||||
|
try:
|
||||||
|
suite.addTest(MissingDatabases('test_assertion_error'))
|
||||||
|
except unittest.SkipTest:
|
||||||
|
self.fail("SkipTest should not be raised at this stage")
|
||||||
|
runner = unittest.TextTestRunner(stream=StringIO())
|
||||||
|
msg = (
|
||||||
|
"skipIfDBFeature cannot be used on <class 'test_utils.tests."
|
||||||
|
"SkippingClassTestCase.test_missing_default_databases.<locals>."
|
||||||
|
"MissingDatabases'> as it doesn't allow queries against the "
|
||||||
|
"'default' database."
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
|
runner.run(suite)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF='test_utils.urls')
|
@override_settings(ROOT_URLCONF='test_utils.urls')
|
||||||
class AssertNumQueriesTests(TestCase):
|
class AssertNumQueriesTests(TestCase):
|
||||||
|
@ -581,7 +581,7 @@ class ForcedTimeZoneDatabaseTests(TransactionTestCase):
|
|||||||
|
|
||||||
@skipUnlessDBFeature('supports_timezones')
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
|
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
|
||||||
class UnsupportedTimeZoneDatabaseTests(SimpleTestCase):
|
class UnsupportedTimeZoneDatabaseTests(TestCase):
|
||||||
|
|
||||||
def test_time_zone_parameter_not_supported_if_database_supports_timezone(self):
|
def test_time_zone_parameter_not_supported_if_database_supports_timezone(self):
|
||||||
connections.databases['tz'] = connections.databases['default'].copy()
|
connections.databases['tz'] = connections.databases['default'].copy()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user