diff options
author | Alex Waygood <Alex.Waygood@Gmail.com> | 2021-11-05 10:06:18 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-05 10:06:18 (GMT) |
commit | 32f55d1a5de66f9a86964fc0655d7a006a9d90b9 (patch) | |
tree | 4f361551a1695706346b380346dd1014819ea28d | |
parent | 3509b26c916707363c71a1df040855e395cf4817 (diff) | |
download | cpython-32f55d1a5de66f9a86964fc0655d7a006a9d90b9.zip cpython-32f55d1a5de66f9a86964fc0655d7a006a9d90b9.tar.gz cpython-32f55d1a5de66f9a86964fc0655d7a006a9d90b9.tar.bz2 |
bpo-45678: Add more ``singledispatchmethod`` tests (GH-29412)
In order to fix a bug in the 3.9 branch in #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.
-rw-r--r-- | Lib/test/test_functools.py | 99 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Tests/2021-11-04-20-03-32.bpo-45678.1xNMjN.rst | 3 |
2 files changed, 102 insertions, 0 deletions
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index 1a3c921..7bc355f 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 0000000..736d5f6 --- /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. |