gh-94924: support inspect.iscoroutinefunction in create_autospec(async_def) (#94962)

* support inspect.iscoroutinefunction in create_autospec(async_def)

* test create_autospec with inspect.iscoroutine and inspect.iscoroutinefunction

* test when create_autospec functions check their signature
This commit is contained in:
Thomas Grainger 2023-06-09 14:29:09 +01:00 committed by GitHub
parent 0f885ffa94
commit 9bf8d825a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 2 deletions

View file

@ -232,7 +232,9 @@ class AsyncAutospecTest(unittest.TestCase):
run(main()) run(main())
self.assertTrue(iscoroutinefunction(spec)) self.assertTrue(iscoroutinefunction(spec))
self.assertTrue(inspect.iscoroutinefunction(spec))
self.assertTrue(asyncio.iscoroutine(awaitable)) self.assertTrue(asyncio.iscoroutine(awaitable))
self.assertTrue(inspect.iscoroutine(awaitable))
self.assertEqual(spec.await_count, 1) self.assertEqual(spec.await_count, 1)
self.assertEqual(spec.await_args, call(1, 2, c=3)) self.assertEqual(spec.await_args, call(1, 2, c=3))
self.assertEqual(spec.await_args_list, [call(1, 2, c=3)]) self.assertEqual(spec.await_args_list, [call(1, 2, c=3)])
@ -244,6 +246,25 @@ class AsyncAutospecTest(unittest.TestCase):
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
spec.assert_any_await(e=1) spec.assert_any_await(e=1)
def test_autospec_checks_signature(self):
spec = create_autospec(async_func_args)
# signature is not checked when called
awaitable = spec()
self.assertListEqual(spec.mock_calls, [])
async def main():
await awaitable
# but it is checked when awaited
with self.assertRaises(TypeError):
run(main())
# _checksig_ raises before running or awaiting the mock
self.assertListEqual(spec.mock_calls, [])
self.assertEqual(spec.await_count, 0)
self.assertIsNone(spec.await_args)
self.assertEqual(spec.await_args_list, [])
spec.assert_not_awaited()
def test_patch_with_autospec(self): def test_patch_with_autospec(self):
@ -253,7 +274,9 @@ class AsyncAutospecTest(unittest.TestCase):
self.assertIsInstance(mock_method.mock, AsyncMock) self.assertIsInstance(mock_method.mock, AsyncMock)
self.assertTrue(iscoroutinefunction(mock_method)) self.assertTrue(iscoroutinefunction(mock_method))
self.assertTrue(inspect.iscoroutinefunction(mock_method))
self.assertTrue(asyncio.iscoroutine(awaitable)) self.assertTrue(asyncio.iscoroutine(awaitable))
self.assertTrue(inspect.iscoroutine(awaitable))
self.assertTrue(inspect.isawaitable(awaitable)) self.assertTrue(inspect.isawaitable(awaitable))
# Verify the default values during mock setup # Verify the default values during mock setup

View file

@ -204,6 +204,33 @@ def _set_signature(mock, original, instance=False):
_setup_func(funcopy, mock, sig) _setup_func(funcopy, mock, sig)
return funcopy return funcopy
def _set_async_signature(mock, original, instance=False, is_async_mock=False):
# creates an async function with signature (*args, **kwargs) that delegates to a
# mock. It still does signature checking by calling a lambda with the same
# signature as the original.
skipfirst = isinstance(original, type)
result = _get_signature_object(original, instance, skipfirst)
if result is None:
return mock
func, sig = result
def checksig(*args, **kwargs):
sig.bind(*args, **kwargs)
_copy_func_details(func, checksig)
name = original.__name__
if not name.isidentifier():
name = 'funcopy'
context = {'_checksig_': checksig, 'mock': mock}
src = """async def %s(*args, **kwargs):
_checksig_(*args, **kwargs)
return await mock(*args, **kwargs)""" % name
exec (src, context)
funcopy = context[name]
_setup_func(funcopy, mock, sig)
_setup_async_mock(funcopy)
return funcopy
def _setup_func(funcopy, mock, sig): def _setup_func(funcopy, mock, sig):
funcopy.mock = mock funcopy.mock = mock
@ -2745,9 +2772,10 @@ def create_autospec(spec, spec_set=False, instance=False, _parent=None,
if isinstance(spec, FunctionTypes): if isinstance(spec, FunctionTypes):
# should only happen at the top level because we don't # should only happen at the top level because we don't
# recurse for functions # recurse for functions
mock = _set_signature(mock, spec)
if is_async_func: if is_async_func:
_setup_async_mock(mock) mock = _set_async_signature(mock, spec)
else:
mock = _set_signature(mock, spec)
else: else:
_check_signature(spec, mock, is_type, instance) _check_signature(spec, mock, is_type, instance)

View file

@ -0,0 +1 @@
:func:`unittest.mock.create_autospec` now properly returns coroutine functions compatible with :func:`inspect.iscoroutinefunction`