summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2021-11-05 08:27:22 -0700
committerGitHub <noreply@github.com>2021-11-05 16:27:22 +0100
commitbcb236c19e4ddf5ccf0fc45ab541eabf1f757a64 (patch)
tree8b0b871eb020cb7ea68485949163db8ae7feb3e4
parent099a94fba3f9437e29d16ed54215ec8cf65de868 (diff)
downloadcpython-git-bcb236c19e4ddf5ccf0fc45ab541eabf1f757a64.tar.gz
bpo-45678: Add more ``singledispatchmethod`` tests (GH-29412) (GH-29424)
In order to fix a bug in the 3.9 branch in GH-29394, more tests were added to ``test_functools.py`` to ensure that ``singledispatchmethod`` still correctly wrapped a target method, even if the target method had already been wrapped by multiple other decorators. This PR brings the new tests into the 3.11 and 3.10 branches as well. (cherry picked from commit 32f55d1a5de66f9a86964fc0655d7a006a9d90b9) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
-rw-r--r--Lib/test/test_functools.py99
-rw-r--r--Misc/NEWS.d/next/Tests/2021-11-04-20-03-32.bpo-45678.1xNMjN.rst3
2 files changed, 102 insertions, 0 deletions
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 1a3c921509..7bc355ff72 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -2519,6 +2519,105 @@ class TestSingleDispatch(unittest.TestCase):
self.assertEqual(A.static_func.__name__, 'static_func')
self.assertEqual(A().static_func.__name__, 'static_func')
+ def test_double_wrapped_methods(self):
+ def classmethod_friendly_decorator(func):
+ wrapped = func.__func__
+ @classmethod
+ @functools.wraps(wrapped)
+ def wrapper(*args, **kwargs):
+ return wrapped(*args, **kwargs)
+ return wrapper
+
+ class WithoutSingleDispatch:
+ @classmethod
+ @contextlib.contextmanager
+ def cls_context_manager(cls, arg: int) -> str:
+ try:
+ yield str(arg)
+ finally:
+ return 'Done'
+
+ @classmethod_friendly_decorator
+ @classmethod
+ def decorated_classmethod(cls, arg: int) -> str:
+ return str(arg)
+
+ class WithSingleDispatch:
+ @functools.singledispatchmethod
+ @classmethod
+ @contextlib.contextmanager
+ def cls_context_manager(cls, arg: int) -> str:
+ """My function docstring"""
+ try:
+ yield str(arg)
+ finally:
+ return 'Done'
+
+ @functools.singledispatchmethod
+ @classmethod_friendly_decorator
+ @classmethod
+ def decorated_classmethod(cls, arg: int) -> str:
+ """My function docstring"""
+ return str(arg)
+
+ # These are sanity checks
+ # to test the test itself is working as expected
+ with WithoutSingleDispatch.cls_context_manager(5) as foo:
+ without_single_dispatch_foo = foo
+
+ with WithSingleDispatch.cls_context_manager(5) as foo:
+ single_dispatch_foo = foo
+
+ self.assertEqual(without_single_dispatch_foo, single_dispatch_foo)
+ self.assertEqual(single_dispatch_foo, '5')
+
+ self.assertEqual(
+ WithoutSingleDispatch.decorated_classmethod(5),
+ WithSingleDispatch.decorated_classmethod(5)
+ )
+
+ self.assertEqual(WithSingleDispatch.decorated_classmethod(5), '5')
+
+ # Behavioural checks now follow
+ for method_name in ('cls_context_manager', 'decorated_classmethod'):
+ with self.subTest(method=method_name):
+ self.assertEqual(
+ getattr(WithSingleDispatch, method_name).__name__,
+ getattr(WithoutSingleDispatch, method_name).__name__
+ )
+
+ self.assertEqual(
+ getattr(WithSingleDispatch(), method_name).__name__,
+ getattr(WithoutSingleDispatch(), method_name).__name__
+ )
+
+ for meth in (
+ WithSingleDispatch.cls_context_manager,
+ WithSingleDispatch().cls_context_manager,
+ WithSingleDispatch.decorated_classmethod,
+ WithSingleDispatch().decorated_classmethod
+ ):
+ with self.subTest(meth=meth):
+ self.assertEqual(meth.__doc__, 'My function docstring')
+ self.assertEqual(meth.__annotations__['arg'], int)
+
+ self.assertEqual(
+ WithSingleDispatch.cls_context_manager.__name__,
+ 'cls_context_manager'
+ )
+ self.assertEqual(
+ WithSingleDispatch().cls_context_manager.__name__,
+ 'cls_context_manager'
+ )
+ self.assertEqual(
+ WithSingleDispatch.decorated_classmethod.__name__,
+ 'decorated_classmethod'
+ )
+ self.assertEqual(
+ WithSingleDispatch().decorated_classmethod.__name__,
+ 'decorated_classmethod'
+ )
+
def test_invalid_registrations(self):
msg_prefix = "Invalid first argument to `register()`: "
msg_suffix = (
diff --git a/Misc/NEWS.d/next/Tests/2021-11-04-20-03-32.bpo-45678.1xNMjN.rst b/Misc/NEWS.d/next/Tests/2021-11-04-20-03-32.bpo-45678.1xNMjN.rst
new file mode 100644
index 0000000000..736d5f65f9
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2021-11-04-20-03-32.bpo-45678.1xNMjN.rst
@@ -0,0 +1,3 @@
+Add tests for scenarios in which :class:`functools.singledispatchmethod` is
+stacked on top of a method that has already been wrapped by two other
+decorators. Patch by Alex Waygood.