Mocked socket interfering with pytest-asyncio's teardown
Closed this issue · 1 comments
Consider these two test cases:
@pytest.mark.asyncio
async def test_mocksocket_mocker(mocker):
mocker.patch('socket.socket', wraps=MockSocket)
pass
@pytest.mark.asyncio
async def test_mocksocket_none():
unittest.mock.patch('socket.socket', wraps=MockSocket)
pass
The top case reports ERROR at teardown of test_mocksocket_mocker
, while the bottom one does not report any error.
The error message in full:
================================================================================================== ERRORS ===================================================================================================
________________________________________________________________________________ ERROR at teardown of test_mocksocket_mocker ________________________________________________________________________________
def _provide_clean_event_loop() -> None:
# At this point, the event loop for the current thread is closed.
# When a user calls asyncio.get_event_loop(), they will get a closed loop.
# In order to avoid this side effect from pytest-asyncio, we need to replace
# the current loop with a fresh one.
# Note that we cannot set the loop to None, because get_event_loop only creates
# a new loop, when set_event_loop has not been called.
policy = asyncio.get_event_loop_policy()
> new_loop = policy.new_event_loop()
.venv\Lib\site-packages\pytest_asyncio\plugin.py:850:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
..\..\..\AppData\Local\Programs\Python\Python311\Lib\asyncio\events.py:699: in new_event_loop
return self._loop_factory()
..\..\..\AppData\Local\Programs\Python\Python311\Lib\asyncio\windows_events.py:315: in __init__
super().__init__(proactor)
..\..\..\AppData\Local\Programs\Python\Python311\Lib\asyncio\proactor_events.py:639: in __init__
self._make_self_pipe()
..\..\..\AppData\Local\Programs\Python\Python311\Lib\asyncio\proactor_events.py:784: in _make_self_pipe
self._ssock, self._csock = socket.socketpair()
..\..\..\AppData\Local\Programs\Python\Python311\Lib\socket.py:631: in socketpair
lsock = socket(family, type, proto)
..\..\..\AppData\Local\Programs\Python\Python311\Lib\unittest\mock.py:1124: in __call__
return self._mock_call(*args, **kwargs)
..\..\..\AppData\Local\Programs\Python\Python311\Lib\unittest\mock.py:1128: in _mock_call
return self._execute_mock_call(*args, **kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <MagicMock name='socket' id='2550221096912'>, args = (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 0), kwargs = {}, effect = None
def _execute_mock_call(self, /, *args, **kwargs):
# separate from _increment_mock_call so that awaited functions are
# executed separately from their call, also AsyncMock overrides this method
effect = self.side_effect
if effect is not None:
if _is_exception(effect):
raise effect
elif not _callable(effect):
result = next(effect)
if _is_exception(result):
raise result
else:
result = effect(*args, **kwargs)
if result is not DEFAULT:
return result
if self._mock_return_value is not DEFAULT:
return self.return_value
if self._mock_delegate and self._mock_delegate.return_value is not DEFAULT:
return self.return_value
if self._mock_wraps is not None:
> return self._mock_wraps(*args, **kwargs)
E TypeError: MockSocket() takes no arguments
..\..\..\AppData\Local\Programs\Python\Python311\Lib\unittest\mock.py:1201: TypeError
--------------------------------------------------------------------------------------------- Captured log call ---------------------------------------------------------------------------------------------
ERROR asyncio:base_events.py:1785 Error on reading from the event loop self pipe
loop: <ProactorEventLoop running=True closed=False debug=False>
Traceback (most recent call last):
File "C:\Users\username\AppData\Local\Programs\Python\Python311\Lib\asyncio\proactor_events.py", line 801, in _loop_self_reading
f = self._proactor.recv(self._ssock, 4096)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\username\AppData\Local\Programs\Python\Python311\Lib\asyncio\windows_events.py", line 462, in recv
if isinstance(conn, socket.socket):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: isinstance() arg 2 must be a type, a tuple of types, or a union
It seems like the mocked socket survived while pytest-asyncio is closing the current event loop and creating a new one.
Also, there seems to be a weird interaction with pytest-asyncio. The error disappears if an event_loop fixture is requested, like so:
@pytest.mark.asyncio
async def test_mocksocket_loop_mocker(event_loop, mocker):
mocker.patch('socket.socket', wraps=MockSocket)
pass
Attached is the test file, full console output, and package versions. This is running on Windows 10.
https://github.com/pytest-dev/pytest-asyncio/files/15039206/pytest-asyncio-mock.zip
I have also reported this problem to pytest-asyncio: pytest-dev/pytest-asyncio#818
From the traceback, seems like the pytest-asyncio is trying to create a new event lopp before the pytest-mock has a chance to uninstall the mock.
I'm closing because I don't see what can be done on pytest-mock's side to prevent this: we remove the mocks during fixture teardown, which is the straightforward way to handle unmocking here.