This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Fix asyncio mock warnings
Type: behavior Stage: resolved
Components: Tests Versions: Python 3.8
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: asvetlov, lisroach, mariocj89, miss-islington, xtreak
Priority: normal Keywords: patch

Created on 2019-05-22 17:56 by asvetlov, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Pull Requests
URL Status Linked Edit
PR 13661 merged xtreak, 2019-05-29 19:00
Messages (9)
msg343223 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2019-05-22 17:56
After merging https://github.com/python/cpython/pull/9296 asyncio test suite prints a lot of warnings like

Exception ignored in: <coroutine object AsyncMockMixin._mock_call at 0x7fb648a1c170>
Traceback (most recent call last):
  File "/home/andrew/projects/cpython/Lib/warnings.py", line 510, in _warn_unawaited_coroutine
    warn(msg, category=RuntimeWarning, stacklevel=2, source=coro)
RuntimeWarning: coroutine 'AsyncMockMixin._mock_call' was never awaited

I believe this is not a sign of AsyncMock problem but asyncio tests should be fixed.
Need more investigations though.
msg343228 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-22 18:36
Not sure if it helps. Set backlog in the below test as 1. Add a print statement print(type(_mock_call)) at [0] . I could see some of _mock_call to be AsyncMock . So instead of calling return _mock_self._mock_call(*args, **kwargs) directly if I call them only with they are not instance of AsyncMock (not isinstance(_mock_self, AsyncMock)) then the warning goes away. 

Guess somewhere an AsyncMock is created as I can see _accept_connection2. When the backlog value is set to 100 it shows lot of warnings.

$ ./python.exe -Werror -X tracemalloc -m unittest -vv test.test_asyncio.test_selector_events.BaseSelectorEventLoopTests.test_accept_connection_multiple


test_accept_connection_multiple (test.test_asyncio.test_selector_events.BaseSelectorEventLoopTests) ... <AsyncMock name='_accept_connection2' id='4465398880'>
<class 'unittest.mock.Mock'>
<class 'unittest.mock.Mock'>
<class 'unittest.mock.AsyncMock'>
<class 'unittest.mock.MagicMock'>
<class 'unittest.mock.Mock'>
ok

----------------------------------------------------------------------
Ran 1 test in 0.123s

OK
Exception ignored in: <coroutine object AsyncMockMixin._mock_call at 0x10a2a0e60>
Traceback (most recent call last):
  File "/Users/karthikeyansingaravelan/stuff/python/cpython/Lib/warnings.py", line 510, in _warn_unawaited_coroutine
    warn(msg, category=RuntimeWarning, stacklevel=2, source=coro)
RuntimeWarning: coroutine 'AsyncMockMixin._mock_call' was never awaited

[0] https://github.com/python/cpython/blob/b121f63155d8e3c7c42ab6122e36eaf7f5e9f7f5/Lib/unittest/mock.py#L991
msg343229 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-22 18:53
patching _accept_connection2 attribute on loop object seems to return an AsyncMock.

➜  cpython git:(master) ✗ cat ../backups/bpo37015.py
import asyncio
from unittest.mock import patch
with patch.object(asyncio.get_event_loop(), '_accept_connection2') as f:
    print(f)
    f()
➜  cpython git:(master) ✗ ./python.exe ../backups/bpo37015.py
<AsyncMock name='_accept_connection2' id='4361411632'>
../backups/bpo37015.py:5: RuntimeWarning: coroutine 'AsyncMockMixin._mock_call' was never awaited
  f()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback


Relevant test

    def test_accept_connection_multiple(self):
        sock = mock.Mock()
        sock.accept.return_value = (mock.Mock(), mock.Mock())
        backlog = 1
        # Mock the coroutine generation for a connection to prevent
        # warnings related to un-awaited coroutines.
        mock_obj = mock.patch.object
        with mock_obj(self.loop, '_accept_connection2') as accept2_mock:
            print(f"{accept2_mock=}")
            accept2_mock.return_value = None
            with mock_obj(self.loop, 'create_task') as task_mock:
                task_mock.return_value = None
                self.loop._accept_connection(
                    mock.Mock(), sock, backlog=backlog)
        self.assertEqual(sock.accept.call_count, backlog)

When I specify new value which defaults to DEFAULT as Mock() then there is no AsyncMock. Same can be done in test and the warnings go away. My suspicion is that if there is a loop object with _accept_connection2 earlier in Python 3.7 <MagicMock name='_accept_connection2'> is returned by patch.object but now it returns an <AsyncMock name='_accept_connection2'> instead

# use explicit mock

import asyncio
from unittest.mock import patch, Mock

with patch.object(asyncio.get_event_loop(), '_accept_connection2', Mock()) as f:
    print(f)
    f()

➜  cpython git:(master) ✗ ./python.exe ../backups/bpo37015.py
<Mock id='4342533248'>
msg343230 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-22 19:16
I guess cause is at [0] . When there is no new value specified and with spec being None if mock.patch is used on an async object (original = _accept_connection2). Here original is an async object then AsyncMock is returned. 

Changing this causes test failures where patching AsyncClass.async_method now expects an AsyncMock but as per older behavior it returns MagicMock. I think it's a behavior that needs to be discussed as it differs from 3.7 and not sure if AsyncMock would always be awaited as seen in test_accept_connection_multiple.

[0] https://github.com/python/cpython/blob/b121f63155d8e3c7c42ab6122e36eaf7f5e9f7f5/Lib/unittest/mock.py#L1313

if spec is None and _is_async_obj(original):
    Klass = AsyncMock
else:
    Klass = MagicMock
msg343352 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-24 08:10
This logic is present in create_autospec too at [0]. The behavior for patch seems to be documented at [1] which also needs to be updated to reflect that an AsyncMock by default is created instead of MagicMock if the target is an async function unlike 3.7. Probably also a versionchanged directive to note this if the current behavior is okay. Since it generates a RuntimeWarning I think it will be good if the behavior is decided before 3.8 beta 1.

> If new is omitted, then the target is replaced with a MagicMock. If patch() is used as a decorator and new is omitted, the created mock is passed in as an extra argument to the decorated function. If patch() is used as a context manager the created mock is returned by the context manager.

$ python3.7 -q
>>> import asyncio
>>> from unittest.mock import create_autospec
>>> loop = asyncio.get_event_loop()
>>> loop_mock = create_autospec(loop)
>>> loop_mock._accept_connection2
<MagicMock name='mock._accept_connection2' spec='method' id='4455012896'>

$ ./python.exe -q
>>> import asyncio
>>> from unittest.mock import create_autospec
>>> loop = asyncio.get_event_loop()
>>> loop_mock = create_autospec(loop)
>>> loop_mock._accept_connection2
<AsyncMock name='mock._accept_connection2' spec='method' id='4314046368'>

[0] https://github.com/python/cpython/blob/cccc11b38e5409861f4db345a4dd45dcc9ba470c/Lib/unittest/mock.py#L2564
[1] https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch
msg343366 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2019-05-24 10:31
xtreak thanks for investigation.
I have no time to work on it now, sorry.
Maybe the next week.
Anyway, the problem can be fixed even in python beta stage if we don't do it earlier.
msg343368 - (view) Author: Karthikeyan Singaravelan (xtreak) * (Python committer) Date: 2019-05-24 10:44
@asvetlov No problem then if it can be fixed after beta.
msg343962 - (view) Author: miss-islington (miss-islington) Date: 2019-05-30 10:00
New changeset 0f39c2b1919727904f4fac2d79cb41dc6bfe41fe by Miss Islington (bot) (Xtreak) in branch 'master':
bpo-37015: Ensure tasks created by _accept_connection2 due to AsyncMock are completed (GH-13661)
https://github.com/python/cpython/commit/0f39c2b1919727904f4fac2d79cb41dc6bfe41fe
msg343964 - (view) Author: Andrew Svetlov (asvetlov) * (Python committer) Date: 2019-05-30 10:07
Fixed
History
Date User Action Args
2022-04-11 14:59:15adminsetgithub: 81196
2019-05-30 10:07:09asvetlovsetstatus: open -> closed
resolution: fixed
messages: + msg343964

stage: patch review -> resolved
2019-05-30 10:00:32miss-islingtonsetnosy: + miss-islington
messages: + msg343962
2019-05-29 19:00:56xtreaksetkeywords: + patch
stage: patch review
pull_requests: + pull_request13552
2019-05-24 10:44:31xtreaksetmessages: + msg343368
2019-05-24 10:31:06asvetlovsetmessages: + msg343366
2019-05-24 08:10:43xtreaksetmessages: + msg343352
2019-05-22 19:16:28xtreaksetnosy: + lisroach, mariocj89
messages: + msg343230
2019-05-22 18:53:39xtreaksetmessages: + msg343229
2019-05-22 18:36:43xtreaksetmessages: + msg343228
2019-05-22 18:02:06xtreaksetnosy: + xtreak
2019-05-22 17:56:30asvetlovsettitle: Fix asyncio mock wranings -> Fix asyncio mock warnings
2019-05-22 17:56:03asvetlovcreate