summaryrefslogtreecommitdiff
path: root/tests/deprecation/test_warn_about_external_use.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/deprecation/test_warn_about_external_use.py')
-rw-r--r--tests/deprecation/test_warn_about_external_use.py178
1 files changed, 178 insertions, 0 deletions
diff --git a/tests/deprecation/test_warn_about_external_use.py b/tests/deprecation/test_warn_about_external_use.py
new file mode 100644
index 0000000000..8bcdf5e729
--- /dev/null
+++ b/tests/deprecation/test_warn_about_external_use.py
@@ -0,0 +1,178 @@
+import inspect
+import sys
+import warnings
+from contextlib import contextmanager
+
+from django.test import SimpleTestCase
+from django.utils.deprecation import RemovedInNextVersionWarning
+
+from . import internal
+
+
+class WarnAboutExternalUseTests(SimpleTestCase):
+ @contextmanager
+ def assertNotWarns(self, category, **kwargs):
+ with warnings.catch_warnings(record=True) as caught_warnings:
+ warnings.filterwarnings("always", category=category, **kwargs)
+ yield caught_warnings
+ self.assertEqual([str(warning) for warning in caught_warnings], [])
+
+ def assertWarningPointsHere(self, warning, *, offset=-1):
+ caller_frame = inspect.currentframe().f_back
+ self.assertEqual(warning.filename, caller_frame.f_code.co_filename)
+ self.assertEqual(warning.lineno, caller_frame.f_lineno + offset)
+
+ def test_external_use_warns(self):
+ msg = "This is deprecated."
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, msg) as warning:
+ internal.deprecated_function(msg, RemovedInNextVersionWarning)
+ self.assertWarningPointsHere(warning)
+
+ def test_internal_use_does_not_warn(self):
+ with self.assertNotWarns(RemovedInNextVersionWarning):
+ internal.one_indirection("This is deprecated.", RemovedInNextVersionWarning)
+
+ def test_external_skip_frames_warns(self):
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ internal.one_indirection(skip_frames=1)
+ self.assertWarningPointsHere(warning)
+
+ def test_internal_skip_frames_does_not_warn(self):
+ with self.assertNotWarns(RemovedInNextVersionWarning):
+ internal.two_indirections(skip_frames=1)
+
+ def test_internal_skip_multiple_frames_does_not_warn(self):
+ with self.assertNotWarns(RemovedInNextVersionWarning):
+ internal.three_indirections(skip_frames=2)
+
+ def test_external_skip_module_name_warns(self):
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ internal.two_indirections(skip_name_prefixes=internal.__name__)
+ self.assertWarningPointsHere(warning)
+
+ def test_internal_skip_module_name_does_not_warn(self):
+ # Treat only the current test module as "internal" for this test.
+ with self.assertNotWarns(RemovedInNextVersionWarning):
+ internal.two_indirections(
+ skip_name_prefixes=internal.__name__,
+ internal_modules=(__name__,),
+ )
+
+ def test_skip_fully_qualified_name(self):
+ fqname = f"{internal.__name__}.Class"
+ instance = internal.Class()
+ for case in ("deprecated_method", "one_indirection", "two_indirections"):
+ method = getattr(instance, case)
+ with self.subTest(use="external warns", case=case):
+ with self.assertWarnsMessage(
+ RemovedInNextVersionWarning, "Message"
+ ) as warning:
+ method(skip_name_prefixes=fqname)
+ self.assertWarningPointsHere(warning)
+ with (
+ self.subTest(use="internal does not warn", case=case),
+ self.assertNotWarns(RemovedInNextVersionWarning),
+ ):
+ # Treat only the current test module as "internal".
+ method(skip_name_prefixes=fqname, internal_modules=(__name__,))
+
+ def test_skip_name_prefixes_tuple(self):
+ prefixes = (
+ internal.__name__,
+ "django.utils.deprecation.deprecate_posargs",
+ )
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ internal.call_decorated(skip_name_prefixes=prefixes)
+ self.assertWarningPointsHere(warning)
+
+ def test_skip_name_prefixes_is_applied_before_skip_frames(self):
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ # Stack frames:
+ # - deprecated_function()
+ # - one_indirection() -- ignored by skip_name_prefixes
+ # - two_indirections() -- ignored by skip_frames=1
+ # - this test case -- effective caller, is external
+ internal.two_indirections(
+ skip_name_prefixes=f"{internal.__name__}.one_indirection",
+ skip_frames=1,
+ )
+ self.assertWarningPointsHere(warning, offset=-4)
+
+ def test_skip_name_prefixes_is_not_applied_after_skip_frames(self):
+ with self.assertNotWarns(RemovedInNextVersionWarning):
+ # Stack frames:
+ # - deprecated_function()
+ # - (skip_name_prefixes does not match here)
+ # - one_indirection() -- ignored by skip_frames=1
+ # - two_indirections() -- effective caller, is internal
+ internal.two_indirections(
+ skip_name_prefixes=f"{internal.__name__}.two_indirections",
+ skip_frames=1,
+ )
+
+ def test_nested_qualname(self):
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ internal.nested(skip_name_prefixes=f"{internal.__name__}.nested")
+ self.assertWarningPointsHere(warning)
+
+ def test_does_not_mistake_third_party_packages_for_django(self):
+ # Simulate a "django_goodies" package (which is not part of Django)
+ # using a deprecated Django feature.
+ sys.modules["django.something"] = internal
+ self.addCleanup(sys.modules.pop, "django.something", None)
+ code = compile(
+ (
+ "from django.something import deprecated_function\n"
+ "\n"
+ "def use_deprecated_function(*args, **kwargs):\n"
+ " deprecated_function(*args, **kwargs)\n"
+ ),
+ filename="/venv/site-packages/django_goodies/__init__.py",
+ mode="exec",
+ )
+ namespace = {"__name__": "django_goodies"}
+ exec(code, namespace)
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ # internal_modules=None forces the default modules.
+ namespace["use_deprecated_function"](internal_modules=None)
+ self.assertEqual(
+ warning.filename, "/venv/site-packages/django_goodies/__init__.py"
+ )
+
+ def test_internal_modules_must_be_tuple(self):
+ with self.assertRaisesMessage(
+ TypeError, "internal_modules must be a tuple of module names"
+ ):
+ internal.deprecated_function(internal_modules="django")
+
+ def test_warns_if_effective_caller_has_no_filename(self):
+ # Simulate a frame whose source location can't be identified by
+ # compiling with an empty filename.
+ code = compile(
+ (
+ "def use_deprecated_function(*args, **kwargs):\n"
+ " deprecated_function(*args, **kwargs)\n"
+ ),
+ filename="",
+ mode="exec",
+ )
+ namespace = {"deprecated_function": internal.deprecated_function}
+ exec(code, namespace)
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ namespace["use_deprecated_function"]()
+ self.assertEqual(warning.filename, "")
+ self.assertEqual(warning.lineno, 2)
+
+ def test_handles_skip_frames_overflow(self):
+ too_many_frames = len(inspect.stack()) + 20
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ internal.deprecated_function(skip_frames=too_many_frames)
+ # In CPython, warning.filename seems to be "<sys>" and warning.lineno
+ # is 0. But the exact values are likely implementation-dependent.
+ self.assertNotEqual(warning.filename, __file__)
+
+ def test_handles_skip_name_prefixes_overflow(self):
+ with self.assertWarnsMessage(RemovedInNextVersionWarning, "Message") as warning:
+ # Every string startswith(""). This will ignore the entire stack.
+ internal.deprecated_function(skip_name_prefixes="")
+ self.assertNotEqual(warning.filename, __file__)