From 03832db418f11a818051eefcd774495798b203af Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 12 Apr 2026 10:38:44 +0100 Subject: [PATCH] Async test improvements for Python 3.14 (#48) Python 3.14 no longer creates an event loop in asyncio.get_event_loop(), so the make_sync() test helper fails with "There is no current event loop in thread 'MainThread'". Run the async tests with pytest-asyncio. Upstream-Status: Backport [https://github.com/miguelgrinberg/simple-websocket/commit/2005dbb96b5b42a4ebf309d10ac1d33daaddb99f] Signed-off-by: Khem Raj --- tests/helpers.py | 25 ------------------------- tests/test_aioclient.py | 39 ++++++++++++++------------------------- tests/test_aioserver.py | 39 +++++++++++++-------------------------- tests/test_server.py | 2 +- 4 files changed, 28 insertions(+), 77 deletions(-) delete mode 100644 tests/helpers.py diff --git a/tests/helpers.py b/tests/helpers.py deleted file mode 100644 index a7c1a46..0000000 --- a/tests/helpers.py +++ /dev/null @@ -1,25 +0,0 @@ -import asyncio -from unittest import mock - - -def AsyncMock(*args, **kwargs): - """Return a mock asynchronous function.""" - m = mock.MagicMock(*args, **kwargs) - - async def mock_coro(*args, **kwargs): - return m(*args, **kwargs) - - mock_coro.mock = m - return mock_coro - - -def _run(coro): - """Run the given coroutine.""" - return asyncio.get_event_loop().run_until_complete(coro) - - -def make_sync(coro): - """Wrap a coroutine so that it can be executed by pytest.""" - def wrapper(*args, **kwargs): - return _run(coro(*args, **kwargs)) - return wrapper diff --git a/tests/test_aioclient.py b/tests/test_aioclient.py index 01a772a..5ec12ff 100644 --- a/tests/test_aioclient.py +++ b/tests/test_aioclient.py @@ -1,15 +1,14 @@ import asyncio -import unittest from unittest import mock import pytest # noqa: F401 from wsproto.events import AcceptConnection, CloseConnection, TextMessage, \ BytesMessage, Ping import simple_websocket -from .helpers import make_sync, AsyncMock -class AioSimpleWebSocketClientTestCase(unittest.TestCase): +@pytest.mark.asyncio +class TestAioSimpleWebSocketClient: async def get_client(self, mock_wsconn, url, events=[], subprotocols=None, headers=None): mock_wsconn().events.side_effect = \ @@ -19,11 +18,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): return await simple_websocket.AioClient.connect( url, subprotocols=subprotocols, headers=headers) - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_make_client(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1') @@ -40,12 +38,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): assert client.port == 80 assert client.path == '/ws?a=1' - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_make_client_subprotocol(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1', @@ -55,12 +52,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): b"Request(host='example.com', target='/ws?a=1', extensions=[], " b"extra_headers=[], subprotocols=['foo'])") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_make_client_subprotocols(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1', @@ -70,12 +66,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): b"Request(host='example.com', target='/ws?a=1', extensions=[], " b"extra_headers=[], subprotocols=['foo', 'bar'])") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_make_client_headers(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client(mock_wsconn, 'ws://example.com/ws?a=1', @@ -84,12 +79,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): b"Request(host='example.com', target='/ws?a=1', extensions=[], " b"extra_headers=[('Foo', 'Bar')], subprotocols=[])") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_make_client_headers2(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client( @@ -100,11 +94,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): b"extra_headers=[('Foo', 'Bar'), ('Foo', 'Baz')], " b"subprotocols=[])") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_send(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client(mock_wsconn, 'ws://example.com/ws') @@ -123,11 +116,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): b"Message(data=b'hello', frame_finished=True, " b"message_finished=True)") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client( @@ -142,12 +134,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): assert await client.receive() == b'hello' assert await client.receive(timeout=0) is None - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_after_close(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client( @@ -160,11 +151,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): with pytest.raises(simple_websocket.ConnectionClosed): await client.receive() - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_ping(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) @@ -176,11 +166,11 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): await asyncio.sleep(0.01) wsock.write.assert_any_call(b"Pong(payload=b'hello')") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_empty(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(side_effect=[b'x', b'x', b''])) + rsock = mock.MagicMock(read=mock.AsyncMock( + side_effect=[b'x', b'x', b''])) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client( @@ -193,11 +183,10 @@ class AioSimpleWebSocketClientTestCase(unittest.TestCase): assert await client.receive() == 'hello' assert await client.receive(timeout=0) is None - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_close(self, mock_wsconn, mock_open_connection): - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) client = await self.get_client( diff --git a/tests/test_aioserver.py b/tests/test_aioserver.py index a274019..071e035 100644 --- a/tests/test_aioserver.py +++ b/tests/test_aioserver.py @@ -1,15 +1,14 @@ import asyncio -import unittest from unittest import mock import pytest # noqa: F401 from wsproto.events import Request, CloseConnection, TextMessage, \ BytesMessage, Ping, Pong import simple_websocket -from .helpers import make_sync, AsyncMock -class AioSimpleWebSocketServerTestCase(unittest.TestCase): +@pytest.mark.asyncio +class TestAioSimpleWebSocketServer: async def get_server(self, mock_wsconn, request, events=[], client_subprotocols=None, server_subprotocols=None, **kwargs): @@ -29,12 +28,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): return await simple_websocket.AioServer.accept( aiohttp=request, subprotocols=server_subprotocols, **kwargs) - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_aiohttp(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request) @@ -53,19 +51,17 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): b'Sec-Websocket-Version: 13\r\n\r\n') assert server.is_server - @make_sync async def test_invalid_request(self): with pytest.raises(ValueError): await simple_websocket.AioServer.accept(aiohttp='foo', asgi='bar') with pytest.raises(ValueError): await simple_websocket.AioServer.accept(asgi='bar', sock='baz') - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_send(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request) @@ -84,12 +80,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): b"Message(data=b'hello', frame_finished=True, " b"message_finished=True)") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -103,13 +98,12 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): assert await server.receive() == b'hello' assert await server.receive(timeout=0) is None - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_after_close(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -121,13 +115,12 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): with pytest.raises(simple_websocket.ConnectionClosed): await server.receive() - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_split_messages(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -151,12 +144,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): assert await server.receive() == b'hello' assert await server.receive(timeout=0) is None - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_ping(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -166,12 +158,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): await asyncio.sleep(0.01) wsock.write.assert_any_call(b"Pong(payload=b'hello')") - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_empty(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -183,12 +174,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): assert await server.receive() == 'hello' assert await server.receive(timeout=0) is None - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_receive_large(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -201,12 +191,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): assert await server.receive() == 'hello' assert await server.receive(timeout=0) is None - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_close(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request) @@ -222,7 +211,6 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): b'CloseConnection(code=, ' b'reason=None)') - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') @mock.patch('simple_websocket.aiows.time') @@ -230,7 +218,7 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): async def test_ping_pong(self, mock_wait_for, mock_time, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock()) + rsock = mock.MagicMock(read=mock.AsyncMock()) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) server = await self.get_server(mock_wsconn, mock_request, events=[ @@ -246,12 +234,11 @@ class AioSimpleWebSocketServerTestCase(unittest.TestCase): assert wsock.write.call_args_list[2][0][0].startswith(b'Ping') assert wsock.write.call_args_list[3][0][0].startswith(b'Close') - @make_sync @mock.patch('simple_websocket.aiows.asyncio.open_connection') @mock.patch('simple_websocket.aiows.WSConnection') async def test_subprotocols(self, mock_wsconn, mock_open_connection): mock_request = mock.MagicMock(headers={}) - rsock = mock.MagicMock(read=AsyncMock(return_value=b'x')) + rsock = mock.MagicMock(read=mock.AsyncMock(return_value=b'x')) wsock = mock.MagicMock() mock_open_connection.return_value = (rsock, wsock) diff --git a/tests/test_server.py b/tests/test_server.py index 7be5d15..ca5fe99 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -92,7 +92,7 @@ class SimpleWebSocketServerTestCase(unittest.TestCase): b"message_finished=True)") server.connected = True server.send(b'hello') - mock_socket.send.assert_called_with( + mock_socket.send.assert_any_call( b"Message(data=b'hello', frame_finished=True, " b"message_finished=True)")