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
|
||||
|
||||
|
||||
def _deferredSkip(condition, reason):
|
||||
def _deferredSkip(condition, reason, name):
|
||||
def decorator(test_func):
|
||||
nonlocal condition
|
||||
if not (isinstance(test_func, type) and
|
||||
issubclass(test_func, unittest.TestCase)):
|
||||
@wraps(test_func)
|
||||
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():
|
||||
raise unittest.SkipTest(reason)
|
||||
return test_func(*args, **kwargs)
|
||||
|
@ -1218,6 +1230,16 @@ def _deferredSkip(condition, reason):
|
|||
else:
|
||||
# Assume a class is decorated
|
||||
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
|
||||
# avoid triggering the descriptor.
|
||||
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."""
|
||||
return _deferredSkip(
|
||||
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."""
|
||||
return _deferredSkip(
|
||||
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."""
|
||||
return _deferredSkip(
|
||||
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):
|
||||
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):
|
||||
self.assertEqual(self.ops.deferrable_sql(), '')
|
||||
|
||||
|
@ -123,6 +117,23 @@ class SimpleDatabaseOperationTests(SimpleTestCase):
|
|||
with self.assertRaisesMessage(NotImplementedError, self.may_requre_msg % 'datetime_extract_sql'):
|
||||
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')
|
||||
def test_subtract_temporals(self):
|
||||
duration_field = DurationField()
|
||||
|
@ -133,13 +144,3 @@ class SimpleDatabaseOperationTests(SimpleTestCase):
|
|||
)
|
||||
with self.assertRaisesMessage(NotSupportedError, msg):
|
||||
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.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.utils.functional import lazy
|
||||
from django.utils.timezone import now
|
||||
|
@ -680,7 +680,7 @@ class TimeFieldTests(SimpleTestCase):
|
|||
|
||||
|
||||
@isolate_apps('invalid_models_tests')
|
||||
class TextFieldTests(SimpleTestCase):
|
||||
class TextFieldTests(TestCase):
|
||||
|
||||
@skipIfDBFeature('supports_index_on_text_field')
|
||||
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.db import models
|
||||
from django.db import connection, models
|
||||
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
|
||||
|
||||
|
||||
|
@ -501,13 +503,14 @@ class RelativeFieldTests(SimpleTestCase):
|
|||
),
|
||||
])
|
||||
|
||||
@skipIfDBFeature('interprets_empty_strings_as_nulls')
|
||||
def test_nullable_primary_key(self):
|
||||
class Model(models.Model):
|
||||
field = models.IntegerField(primary_key=True, null=True)
|
||||
|
||||
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(
|
||||
'Primary keys must not have null=True.',
|
||||
hint='Set null=False on the field, or remove primary_key=True argument.',
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from django.conf import settings
|
||||
from django.db import connection, models
|
||||
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 .models import Book, ChildModel1, ChildModel2
|
||||
|
||||
|
||||
class IndexesTests(SimpleTestCase):
|
||||
class SimpleIndexesTests(SimpleTestCase):
|
||||
|
||||
def test_suffix(self):
|
||||
self.assertEqual(models.Index.suffix, 'idx')
|
||||
|
@ -156,6 +156,9 @@ class IndexesTests(SimpleTestCase):
|
|||
index_names = [index.name for index in ChildModel2._meta.indexes]
|
||||
self.assertEqual(index_names, ['model_index_name_b6c374_idx'])
|
||||
|
||||
|
||||
class IndexesTests(TestCase):
|
||||
|
||||
@skipUnlessDBFeature('supports_tablespaces')
|
||||
def test_db_tablespace(self):
|
||||
editor = connection.schema_editor()
|
||||
|
|
|
@ -29,15 +29,14 @@ from .views import empty_response
|
|||
|
||||
|
||||
class SkippingTestCase(SimpleTestCase):
|
||||
def _assert_skipping(self, func, expected_exc):
|
||||
# We cannot simply use assertRaises because a SkipTest exception will go unnoticed
|
||||
def _assert_skipping(self, func, expected_exc, msg=None):
|
||||
try:
|
||||
func()
|
||||
except expected_exc:
|
||||
pass
|
||||
except Exception as e:
|
||||
self.fail("No %s exception should have been raised for %s." % (
|
||||
e.__class__.__name__, func.__name__))
|
||||
if msg is not None:
|
||||
self.assertRaisesMessage(expected_exc, msg, func)
|
||||
else:
|
||||
self.assertRaises(expected_exc, func)
|
||||
except unittest.SkipTest:
|
||||
self.fail('%s should not result in a skipped test.' % func.__name__)
|
||||
|
||||
def test_skip_unless_db_feature(self):
|
||||
"""
|
||||
|
@ -65,6 +64,20 @@ class SkippingTestCase(SimpleTestCase):
|
|||
self._assert_skipping(test_func3, ValueError)
|
||||
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):
|
||||
"""
|
||||
Testing the django.test.skipIfDBFeature decorator.
|
||||
|
@ -95,17 +108,31 @@ class SkippingTestCase(SimpleTestCase):
|
|||
self._assert_skipping(test_func4, unittest.SkipTest)
|
||||
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):
|
||||
@skipUnlessDBFeature("__class__")
|
||||
class NotSkippedTests(unittest.TestCase):
|
||||
class NotSkippedTests(TestCase):
|
||||
def test_dummy(self):
|
||||
return
|
||||
|
||||
@skipUnlessDBFeature("missing")
|
||||
@skipIfDBFeature("__class__")
|
||||
class SkippedTests(unittest.TestCase):
|
||||
class SkippedTests(TestCase):
|
||||
def test_will_be_skipped(self):
|
||||
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(SkippedTestsSubclass('test_will_be_skipped'))
|
||||
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)
|
||||
self.assertEqual(result.testsRun, 3)
|
||||
self.assertEqual(len(result.skipped), 2)
|
||||
self.assertEqual(result.skipped[0][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')
|
||||
class AssertNumQueriesTests(TestCase):
|
||||
|
|
|
@ -581,7 +581,7 @@ class ForcedTimeZoneDatabaseTests(TransactionTestCase):
|
|||
|
||||
@skipUnlessDBFeature('supports_timezones')
|
||||
@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):
|
||||
connections.databases['tz'] = connections.databases['default'].copy()
|
||||
|
|
Loading…
Reference in New Issue