Skip to content

Commit

Permalink
Added Python 3.12 to CI (#159)
Browse files Browse the repository at this point in the history
  • Loading branch information
francis-clairicia authored Nov 11, 2023
1 parent 5aac2b8 commit 7d24046
Show file tree
Hide file tree
Showing 17 changed files with 188 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .github/codecov.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
comment:
require_changes: false # Always show messages if codecov is triggered
layout: "flags" # Only show coverage by flags
after_n_builds: 4 # nb_tested_python_version(==1) * nb_tested_platforms(==3) + 1
after_n_builds: 8 # nb_tested_python_version(==2) * (nb_tested_platforms(==3) + nb_unit_test_runs(==1))
coverage:
status:
project:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python_version: ['3.11']
python_version: ['3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-22.04, windows-2022, macos-12]
python_version: ['3.11']
python_version: ['3.11', '3.12']

steps:
- uses: actions/checkout@v4
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Internet",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: System :: Networking",
Expand Down
6 changes: 5 additions & 1 deletion src/easynetwork/lowlevel/asyncio/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,11 @@ def get_cancelled_exc_class(self) -> type[BaseException]:
async def ignore_cancellation(self, coroutine: Coroutine[Any, Any, _T_co]) -> _T_co:
if not asyncio.iscoroutine(coroutine):
raise TypeError("Expected a coroutine object")
return await TaskUtils.cancel_shielded_await_task(asyncio.create_task(coroutine))
if sys.version_info >= (3, 12):
context = TaskUtils.current_asyncio_task().get_context()
else:
context = None
return await TaskUtils.cancel_shielded_await_task(asyncio.create_task(coroutine, context=context))

def open_cancel_scope(self, *, deadline: float = math.inf) -> CancelScope:
return CancelScope(deadline=deadline)
Expand Down
13 changes: 6 additions & 7 deletions src/easynetwork/lowlevel/asyncio/datagram/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,15 @@ def __init__(
self.__transport: asyncio.DatagramTransport = transport
self.__protocol: DatagramEndpointProtocol = protocol

def close(self) -> None:
self.__transport.close()
async def aclose(self) -> None:
if self.__transport.is_closing():
# Only wait for it.
await self.__protocol._get_close_waiter()
return

async def wait_closed(self) -> None:
self.__transport.close()
await self.__protocol._get_close_waiter()

async def aclose(self) -> None:
self.close()
await self.wait_closed()

def is_closing(self) -> bool:
return self.__transport.is_closing()

Expand Down
7 changes: 1 addition & 6 deletions src/easynetwork/lowlevel/asyncio/datagram/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,7 @@ def is_closing(self) -> bool:

async def aclose(self) -> None:
self.__closing = True
self.__endpoint.close()
try:
await self.__endpoint.wait_closed()
except asyncio.CancelledError:
self.__endpoint.transport.abort()
raise
await self.__endpoint.aclose()

async def recv_from(self) -> tuple[bytes, tuple[Any, ...]]:
return await self.__endpoint.recvfrom()
Expand Down
7 changes: 1 addition & 6 deletions src/easynetwork/lowlevel/asyncio/datagram/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ def __init__(self, endpoint: DatagramEndpoint) -> None:

async def aclose(self) -> None:
self.__closing = True
self.__endpoint.close()
try:
return await self.__endpoint.wait_closed()
except asyncio.CancelledError:
self.__endpoint.transport.abort()
raise
await self.__endpoint.aclose()

def is_closing(self) -> bool:
return self.__closing
Expand Down
20 changes: 14 additions & 6 deletions src/easynetwork/lowlevel/asyncio/stream/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,20 +64,28 @@ def __init__(

async def aclose(self) -> None:
self.__closing = True
if not self.__writer.is_closing():
if self.__writer.is_closing():
# Only wait for it.
try:
if self.__writer.can_write_eof():
self.__writer.write_eof()
await self.__writer.wait_closed()
except OSError:
pass
finally:
self.__writer.close()
return

try:
if self.__writer.can_write_eof():
self.__writer.write_eof()
except OSError:
pass
finally:
self.__writer.close()
try:
await self.__writer.wait_closed()
except OSError:
pass
except asyncio.CancelledError:
self.__writer.transport.abort()
if self.__writer.get_extra_info("sslcontext") is not None:
self.__writer.transport.abort()
raise

def is_closing(self) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
import pytest_asyncio


async def readline(loop: asyncio.AbstractEventLoop, sock: Socket) -> bytes:
buf: list[bytes] = []
while True:
chunk = await loop.sock_recv(sock, 1024)
if not chunk:
break
buf.append(chunk)
if b"\n" in chunk:
break
return b"".join(buf)


@pytest.mark.asyncio
@pytest.mark.usefixtures("simulate_no_ssl_module")
class TestAsyncTCPNetworkClient:
Expand Down Expand Up @@ -56,7 +68,7 @@ async def test____send_packet____default(
server: Socket,
) -> None:
await client.send_packet("ABCDEF")
assert await event_loop.sock_recv(server, 1024) == b"ABCDEF\n"
assert await readline(event_loop, server) == b"ABCDEF\n"

async def test____send_packet____connection_error____fresh_connection_closed_by_server(
self,
Expand All @@ -76,7 +88,7 @@ async def test____send_packet____connection_error____after_previous_successful_t
server: Socket,
) -> None:
await client.send_packet("ABCDEF")
assert await event_loop.sock_recv(server, 1024) == b"ABCDEF\n"
assert await readline(event_loop, server) == b"ABCDEF\n"
server.close()
with pytest.raises(ConnectionAbortedError):
for _ in range(3): # Windows and macOS catch the issue after several send()
Expand Down Expand Up @@ -109,7 +121,7 @@ async def test____send_eof____close_write_stream(
server: Socket,
) -> None:
await client.send_eof()
assert await event_loop.sock_recv(server, 1024) == b""
assert await readline(event_loop, server) == b""
with pytest.raises(RuntimeError):
await client.send_packet("ABC")
await event_loop.sock_sendall(server, b"ABCDEF\n")
Expand All @@ -129,7 +141,7 @@ async def test____send_eof____idempotent(
server: Socket,
) -> None:
await client.send_eof()
assert await event_loop.sock_recv(server, 1024) == b""
assert await readline(event_loop, server) == b""
await client.send_eof()
await client.send_eof()

Expand Down Expand Up @@ -191,7 +203,7 @@ async def test____recv_packet____eof____shutdown_write_only(
await client.recv_packet()

await client.send_packet("ABCDEF")
assert await event_loop.sock_recv(server, 1024) == b"ABCDEF\n"
assert await readline(event_loop, server) == b"ABCDEF\n"

async def test____recv_packet____client_close_error(self, client: AsyncTCPNetworkClient[str, str]) -> None:
await client.aclose()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ async def factory() -> DatagramEndpoint:
family=socket_family,
local_addr=(localhost_ip, 0),
)
stack.push_async_callback(lambda: asyncio.wait_for(endpoint.wait_closed(), 3))
stack.callback(endpoint.close)
stack.push_async_callback(lambda: asyncio.wait_for(endpoint.aclose(), 3))
return endpoint

yield factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ async def factory() -> DatagramEndpoint:
local_addr=(localhost_ip, 0),
remote_addr=server_address,
)
stack.push_async_callback(endpoint.wait_closed)
stack.callback(endpoint.close)
stack.push_async_callback(endpoint.aclose)
return endpoint

yield factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@
from .....tools import TimeTest


def readline(sock: Socket) -> bytes:
buf: list[bytes] = []
while True:
chunk = sock.recv(1024)
if not chunk:
break
buf.append(chunk)
if b"\n" in chunk:
break
return b"".join(buf)


@pytest.mark.usefixtures("simulate_no_ssl_module")
class TestTCPNetworkClient:
@pytest.fixture
Expand Down Expand Up @@ -46,7 +58,7 @@ def test____close____idempotent(self, client: TCPNetworkClient[str, str]) -> Non

def test____send_packet____default(self, client: TCPNetworkClient[str, str], server: Socket) -> None:
client.send_packet("ABCDEF")
assert server.recv(1024) == b"ABCDEF\n"
assert readline(server) == b"ABCDEF\n"

def test____send_packet____connection_error____fresh_connection_closed_by_server(
self,
Expand All @@ -65,7 +77,7 @@ def test____send_packet____connection_error____after_previous_successful_try(
server: Socket,
) -> None:
client.send_packet("ABCDEF")
assert server.recv(1024) == b"ABCDEF\n"
assert readline(server) == b"ABCDEF\n"
server.close()
with pytest.raises(ConnectionAbortedError):
for _ in range(3): # Windows and macOS catch the issue after several send()
Expand Down Expand Up @@ -94,7 +106,7 @@ def test____send_eof____close_write_stream(
server: Socket,
) -> None:
client.send_eof()
assert server.recv(1024) == b""
assert readline(server) == b""
with pytest.raises(RuntimeError):
client.send_packet("ABC")

Expand All @@ -114,7 +126,7 @@ def test____send_eof____idempotent(
server: Socket,
) -> None:
client.send_eof()
assert server.recv(1024) == b""
assert readline(server) == b""
client.send_eof()
client.send_eof()

Expand Down Expand Up @@ -183,7 +195,7 @@ def test____recv_packet____eof____shutdown_write_only(self, client: TCPNetworkCl
client.recv_packet()

client.send_packet("ABCDEF")
assert server.recv(1024) == b"ABCDEF\n"
assert readline(server) == b"ABCDEF\n"

def test____recv_packet____client_close_error(self, client: TCPNetworkClient[str, str]) -> None:
client.close()
Expand Down
15 changes: 11 additions & 4 deletions tests/pytest_plugins/asyncio_event_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ def _get_windows_proactor_policy() -> type[asyncio.AbstractEventLoopPolicy] | No
return getattr(asyncio, "WindowsProactorEventLoopPolicy", None)


def _get_uvloop_policy() -> type[asyncio.AbstractEventLoopPolicy] | None:
try:
uvloop: Any = importlib.import_module("uvloop")
except ModuleNotFoundError:
return None
return getattr(uvloop, "EventLoopPolicy", None)


def _set_event_loop_policy_according_to_configuration(config: pytest.Config) -> None:
event_loop: EventLoop = config.getoption(ASYNCIO_EVENT_LOOP_OPTION)
match event_loop:
Expand All @@ -48,12 +56,11 @@ def _set_event_loop_policy_according_to_configuration(config: pytest.Config) ->
raise pytest.UsageError(f"{event_loop} event loop is not available in this platform")
asyncio.set_event_loop_policy(WindowsProactorEventLoopPolicy())
case EventLoop.UVLOOP:
try:
uvloop: Any = importlib.import_module("uvloop")
except ModuleNotFoundError:
UVLoopPolicy = _get_uvloop_policy()
if UVLoopPolicy is None:
raise pytest.UsageError(f"{event_loop} event loop is not available in this platform")

uvloop.install()
asyncio.set_event_loop_policy(UVLoopPolicy())
case _:
assert_never(event_loop)

Expand Down
5 changes: 5 additions & 0 deletions tests/unit_test/test_async/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ def __mute_socket_getaddrinfo(module_mocker: MockerFixture) -> None:
module_mocker.patch("socket.getaddrinfo", autospec=True, side_effect=gaierror(EAI_NONAME, "Name or service not known"))


@pytest.fixture(autouse=True)
def __increase_event_loop_execution_time_before_warning(event_loop: asyncio.AbstractEventLoop) -> None:
event_loop.slow_callback_duration = 5.0


@pytest.fixture(scope="session")
def fake_cancellation_cls() -> type[BaseException]:
return FakeCancellation
Expand Down
Loading

0 comments on commit 7d24046

Please sign in to comment.