[5.1.x] Fixed #35666 -- Documented stacklevel usage and testing, and adjusted test suite accordingly.

Over the years we've had multiple instances of hit and misses when
emitting warnings: either setting the wrong stacklevel or not setting
it at all.

This work adds assertions for the existing warnings that were declaring
the correct stacklevel, but were lacking tests for it.

Backport of 57307bbc7d from main.
This commit is contained in:
Simon Charette 2024-08-09 13:03:24 -04:00 committed by Natalia
parent dd58edcc37
commit 9a461cae3e
16 changed files with 84 additions and 39 deletions

View File

@ -206,9 +206,10 @@ You should also add a test for the deprecation warning::
def test_foo_deprecation_warning(self):
msg = "Expected deprecation message"
with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg):
with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg) as ctx:
# invoke deprecated behavior
...
self.assertEqual(ctx.filename, __file__)
It's important to include a ``RemovedInDjangoXXWarning`` comment above code
which has no warning reference, but will need to be changed or removed when the
@ -230,6 +231,7 @@ deprecation ends. For example::
warnings.warn(
"foo() is deprecated.",
category=RemovedInDjangoXXWarning,
stacklevel=2,
)
old_private_helper()
...

View File

@ -240,7 +240,7 @@ class LogEntryTests(TestCase):
def test_log_action(self):
msg = "LogEntryManager.log_action() is deprecated. Use log_actions() instead."
content_type_val = ContentType.objects.get_for_model(Article).pk
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
log_entry = LogEntry.objects.log_action(
self.user.pk,
content_type_val,
@ -250,6 +250,7 @@ class LogEntryTests(TestCase):
change_message="Changed something else",
)
self.assertEqual(log_entry, LogEntry.objects.latest("id"))
self.assertEqual(ctx.filename, __file__)
def test_log_actions(self):
queryset = Article.objects.all().order_by("-id")
@ -297,9 +298,12 @@ class LogEntryTests(TestCase):
msg = (
"The usage of log_action() is deprecated. Implement log_actions() instead."
)
with self.assertNumQueries(3):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
LogEntry.objects2.log_actions(self.user.pk, queryset, DELETION)
with (
self.assertNumQueries(3),
self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx,
):
LogEntry.objects2.log_actions(self.user.pk, queryset, DELETION)
self.assertEqual(ctx.filename, __file__)
log_values = (
LogEntry.objects.filter(action_flag=DELETION)
.order_by("id")

View File

@ -387,15 +387,19 @@ class CheckConstraintTests(TestCase):
def test_check_deprecation(self):
msg = "CheckConstraint.check is deprecated in favor of `.condition`."
condition = models.Q(foo="bar")
with self.assertWarnsRegex(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
constraint = models.CheckConstraint(name="constraint", check=condition)
with self.assertWarnsRegex(RemovedInDjango60Warning, msg):
self.assertEqual(ctx.filename, __file__)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
self.assertIs(constraint.check, condition)
self.assertEqual(ctx.filename, __file__)
other_condition = models.Q(something="else")
with self.assertWarnsRegex(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
constraint.check = other_condition
with self.assertWarnsRegex(RemovedInDjango60Warning, msg):
self.assertEqual(ctx.filename, __file__)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
self.assertIs(constraint.check, other_condition)
self.assertEqual(ctx.filename, __file__)
def test_database_default(self):
models.CheckConstraint(

View File

@ -98,8 +98,9 @@ class GetPrefetchQuerySetDeprecation(TestCase):
"get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
"instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
questions[0].answer_set.get_prefetch_queryset(questions)
self.assertEqual(ctx.filename, __file__)
def test_generic_foreign_key_warning(self):
answers = Answer.objects.all()
@ -107,8 +108,9 @@ class GetPrefetchQuerySetDeprecation(TestCase):
"get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
"instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
Answer.question.get_prefetch_queryset(answers)
self.assertEqual(ctx.filename, __file__)
class GetPrefetchQuerySetsTests(TestCase):

View File

@ -25,12 +25,14 @@ class RenameMethodsTests(SimpleTestCase):
the faulty method.
"""
msg = "`Manager.old` method should be renamed `new`."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
class Manager(metaclass=RenameManagerMethods):
def old(self):
pass
self.assertEqual(ctx.filename, __file__)
def test_get_new_defined(self):
"""
Ensure `old` complains and not `new` when only `new` is defined.
@ -48,20 +50,23 @@ class RenameMethodsTests(SimpleTestCase):
self.assertEqual(len(recorded), 0)
msg = "`Manager.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
manager.old()
self.assertEqual(ctx.filename, __file__)
def test_get_old_defined(self):
"""
Ensure `old` complains when only `old` is defined.
"""
msg = "`Manager.old` method should be renamed `new`."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
class Manager(metaclass=RenameManagerMethods):
def old(self):
pass
self.assertEqual(ctx.filename, __file__)
manager = Manager()
with warnings.catch_warnings(record=True) as recorded:
@ -70,8 +75,9 @@ class RenameMethodsTests(SimpleTestCase):
self.assertEqual(len(recorded), 0)
msg = "`Manager.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
manager.old()
self.assertEqual(ctx.filename, __file__)
def test_deprecated_subclass_renamed(self):
"""
@ -84,21 +90,25 @@ class RenameMethodsTests(SimpleTestCase):
pass
msg = "`Deprecated.old` method should be renamed `new`."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
class Deprecated(Renamed):
def old(self):
super().old()
self.assertEqual(ctx.filename, __file__)
deprecated = Deprecated()
msg = "`Renamed.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
deprecated.new()
self.assertEqual(ctx.filename, __file__)
msg = "`Deprecated.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
deprecated.old()
self.assertEqual(ctx.filename, __file__)
def test_renamed_subclass_deprecated(self):
"""
@ -106,12 +116,14 @@ class RenameMethodsTests(SimpleTestCase):
`old` subclass one that didn't.
"""
msg = "`Deprecated.old` method should be renamed `new`."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
class Deprecated(metaclass=RenameManagerMethods):
def old(self):
pass
self.assertEqual(ctx.filename, __file__)
class Renamed(Deprecated):
def new(self):
super().new()
@ -124,8 +136,9 @@ class RenameMethodsTests(SimpleTestCase):
self.assertEqual(len(recorded), 0)
msg = "`Renamed.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
renamed.old()
self.assertEqual(ctx.filename, __file__)
def test_deprecated_subclass_renamed_and_mixins(self):
"""
@ -147,20 +160,24 @@ class RenameMethodsTests(SimpleTestCase):
super().old()
msg = "`DeprecatedMixin.old` method should be renamed `new`."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
class Deprecated(DeprecatedMixin, RenamedMixin, Renamed):
pass
self.assertEqual(ctx.filename, __file__)
deprecated = Deprecated()
msg = "`RenamedMixin.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
deprecated.new()
self.assertEqual(ctx.filename, __file__)
msg = "`DeprecatedMixin.old` is deprecated, use `new` instead."
with self.assertWarnsMessage(DeprecationWarning, msg):
with self.assertWarnsMessage(DeprecationWarning, msg) as ctx:
deprecated.old()
self.assertEqual(ctx.filename, __file__)
def test_removedafternextversionwarning_pending(self):
self.assertTrue(

View File

@ -163,9 +163,10 @@ class URLFieldAssumeSchemeDeprecationTest(FormFieldAssertionsMixin, SimpleTestCa
"or set the FORMS_URLFIELD_ASSUME_HTTPS transitional setting to True to "
"opt into using 'https' as the new default scheme."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
f = URLField()
self.assertEqual(f.clean("example.com"), "http://example.com")
self.assertEqual(ctx.filename, __file__)
@ignore_warnings(category=RemovedInDjango60Warning)
def test_urlfield_forms_urlfield_assume_https(self):

View File

@ -972,6 +972,7 @@ class DeprecationTests(SimpleTestCase):
def test_coord_setter_deprecation(self):
geom = OGRGeometry("POINT (1 2)")
msg = "coord_dim setter is deprecated. Use set_3d() instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
geom.coord_dim = 3
self.assertEqual(geom.coord_dim, 3)
self.assertEqual(ctx.filename, __file__)

View File

@ -207,16 +207,18 @@ class GeoLite2Test(SimpleTestCase):
def test_coords_deprecation_warning(self):
g = GeoIP2()
msg = "GeoIP2.coords() is deprecated. Use GeoIP2.lon_lat() instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
e1, e2 = g.coords(self.ipv4_str)
self.assertIsInstance(e1, float)
self.assertIsInstance(e2, float)
self.assertEqual(ctx.filename, __file__)
def test_open_deprecation_warning(self):
msg = "GeoIP2.open() is deprecated. Use GeoIP2() instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
g = GeoIP2.open(settings.GEOIP_PATH, GeoIP2.MODE_AUTO)
self.assertTrue(g._reader)
self.assertEqual(ctx.filename, __file__)
@skipUnless(HAS_GEOIP2, "GeoIP2 is required.")

View File

@ -583,8 +583,9 @@ class ManyToManyTests(TestCase):
"get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
"instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
self.a1.publications.get_prefetch_queryset(articles)
self.assertEqual(ctx.filename, __file__)
def test_get_prefetch_querysets_invalid_querysets_length(self):
articles = Article.objects.all()

View File

@ -894,8 +894,9 @@ class ManyToOneTests(TestCase):
"get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
"instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
City.country.get_prefetch_queryset(cities)
self.assertEqual(ctx.filename, __file__)
def test_get_prefetch_queryset_reverse_warning(self):
usa = Country.objects.create(name="United States")
@ -905,8 +906,9 @@ class ManyToOneTests(TestCase):
"get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
"instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
usa.cities.get_prefetch_queryset(countries)
self.assertEqual(ctx.filename, __file__)
def test_get_prefetch_querysets_invalid_querysets_length(self):
City.objects.create(name="Chicago")

View File

@ -328,5 +328,6 @@ class ChoicesMetaDeprecationTests(SimpleTestCase):
from django.db.models import enums
msg = "ChoicesMeta is deprecated in favor of ChoicesType."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
enums.ChoicesMeta
self.assertEqual(ctx.filename, __file__)

View File

@ -928,8 +928,9 @@ class ModelAdminTests(TestCase):
mock_request.user = User.objects.create(username="bill")
content_type = get_content_type_for_model(self.band)
msg = "ModelAdmin.log_deletion() is deprecated. Use log_deletions() instead."
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
created = ma.log_deletion(mock_request, self.band, str(self.band))
self.assertEqual(ctx.filename, __file__)
fetched = LogEntry.objects.filter(action_flag=DELETION).latest("id")
self.assertEqual(created, fetched)
self.assertEqual(fetched.action_flag, DELETION)
@ -966,8 +967,9 @@ class ModelAdminTests(TestCase):
"instead."
)
with self.assertNumQueries(3):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
ima.log_deletions(mock_request, queryset)
self.assertEqual(ctx.filename, __file__)
logs = (
LogEntry.objects.filter(action_flag=DELETION)
.order_by("id")

View File

@ -612,8 +612,9 @@ class OneToOneTests(TestCase):
"get_prefetch_queryset() is deprecated. Use get_prefetch_querysets() "
"instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
Place.bar.get_prefetch_queryset(places)
self.assertEqual(ctx.filename, __file__)
def test_get_prefetch_querysets_invalid_querysets_length(self):
places = Place.objects.all()

View File

@ -2022,13 +2022,15 @@ class DeprecationTests(TestCase):
"get_current_querysets() instead."
)
authors = Author.objects.all()
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
self.assertEqual(
Prefetch("authors", authors).get_current_queryset(1),
authors,
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
self.assertEqual(ctx.filename, __file__)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
self.assertIsNone(Prefetch("authors").get_current_queryset(1))
self.assertEqual(ctx.filename, __file__)
@ignore_warnings(category=RemovedInDjango60Warning)
def test_prefetch_one_level_fallback(self):

View File

@ -212,10 +212,11 @@ class SimplifiedURLTests(SimpleTestCase):
"converters is deprecated and will be removed in Django 6.0."
)
try:
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
register_converter(IntConverter, "int")
finally:
REGISTERED_CONVERTERS.pop("int", None)
self.assertEqual(ctx.filename, __file__)
def test_warning_override_converter(self):
# RemovedInDjango60Warning: when the deprecation ends, replace with
@ -226,11 +227,12 @@ class SimplifiedURLTests(SimpleTestCase):
"registered converters is deprecated and will be removed in Django 6.0."
)
try:
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
register_converter(Base64Converter, "base64")
register_converter(Base64Converter, "base64")
finally:
REGISTERED_CONVERTERS.pop("base64", None)
self.assertEqual(ctx.filename, __file__)
def test_invalid_view(self):
msg = "view must be a callable or a list/tuple in the case of include()."

View File

@ -11,5 +11,6 @@ class TestIterCompat(SimpleTestCase):
"django.utils.itercompat.is_iterable() is deprecated. "
"Use isinstance(..., collections.abc.Iterable) instead."
)
with self.assertWarnsMessage(RemovedInDjango60Warning, msg):
with self.assertWarnsMessage(RemovedInDjango60Warning, msg) as ctx:
is_iterable([])
self.assertEqual(ctx.filename, __file__)