From 96340687c2c2bb0cb147b473ef740d4167dc2490 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Thu, 2 Nov 2023 16:57:34 +0100
Subject: [PATCH 1/5] add tests and coverage ignores for testing/_fake_net to
reach full coverage, and some minor fixes
---
trio/_tests/test_fakenet.py | 177 ++++++++++++++++++++++++++++++++++++
trio/testing/_fake_net.py | 78 ++++++++++------
2 files changed, 225 insertions(+), 30 deletions(-)
diff --git a/trio/_tests/test_fakenet.py b/trio/_tests/test_fakenet.py
index d250a105a3..cbb6a5c431 100644
--- a/trio/_tests/test_fakenet.py
+++ b/trio/_tests/test_fakenet.py
@@ -1,4 +1,6 @@
import errno
+import re
+import socket
import pytest
@@ -26,6 +28,11 @@ async def test_basic_udp() -> None:
await s1.bind(("192.0.2.1", 0))
assert exc.value.errno == errno.EINVAL
+ # Cannot bind multiple sockets to the same address
+ with pytest.raises(OSError) as exc:
+ await s2.bind(("127.0.0.1", port))
+ assert exc.value.errno == errno.EADDRINUSE
+
await s2.sendto(b"xyz", s1.getsockname())
data, addr = await s1.recvfrom(10)
assert data == b"xyz"
@@ -45,7 +52,177 @@ async def test_msg_trunc() -> None:
data, addr = await s1.recvfrom(10)
+async def test_recv_methods() -> None:
+ """Test all recv methods for codecov"""
+ fn()
+ s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+
+ # receiving on an unbound socket is a bad idea (I think?)
+ with pytest.raises(NotImplementedError, match="code will most likely hang"):
+ await s2.recv(10)
+
+ await s1.bind(("127.0.0.1", 0))
+ ip, port = s1.getsockname()
+ assert ip == "127.0.0.1"
+ assert port != 0
+
+ # recvfrom
+ await s2.sendto(b"abc", s1.getsockname())
+ data, addr = await s1.recvfrom(10)
+ assert data == b"abc"
+ assert addr == s2.getsockname()
+
+ # recv
+ await s1.sendto(b"def", s2.getsockname())
+ data = await s2.recv(10)
+ assert data == b"def"
+
+ # recvfrom_into
+ assert await s1.sendto(b"ghi", s2.getsockname()) == 3
+ buf = bytearray(10)
+
+ with pytest.raises(NotImplementedError, match="partial recvfrom_into"):
+ (nbytes, addr) = await s2.recvfrom_into(buf, nbytes=2)
+
+ (nbytes, addr) = await s2.recvfrom_into(buf)
+ assert nbytes == 3
+ assert buf == b"ghi" + b"\x00" * 7
+ assert addr == s1.getsockname()
+
+ # recv_into
+ assert await s1.sendto(b"jkl", s2.getsockname()) == 3
+ buf2 = bytearray(10)
+ nbytes = await s2.recv_into(buf2)
+ assert nbytes == 3
+ assert buf2 == b"jkl" + b"\x00" * 7
+
+ # recvmsg_into
+ assert await s1.sendto(b"xyzw", s2.getsockname()) == 4
+ buf1 = bytearray(2)
+ buf2 = bytearray(3)
+ ret = await s2.recvmsg_into([buf1, buf2])
+ (nbytes, ancdata, msg_flags, addr) = ret
+ assert nbytes == 4
+ assert buf1 == b"xy"
+ assert buf2 == b"zw" + b"\x00"
+ assert ancdata == []
+ assert msg_flags == 0
+ assert addr == s1.getsockname()
+
+ # recvmsg_into with MSG_TRUNC set
+ assert await s1.sendto(b"xyzwv", s2.getsockname()) == 5
+ buf1 = bytearray(2)
+ ret = await s2.recvmsg_into([buf1])
+ (nbytes, ancdata, msg_flags, addr) = ret
+ assert nbytes == 2
+ assert buf1 == b"xy"
+ assert ancdata == []
+ assert msg_flags == socket.MSG_TRUNC
+ assert addr == s1.getsockname()
+
+ # Send seems explicitly non-functional
+ with pytest.raises(OSError, match="Transport endpoint is not connected"):
+ await s2.send(b"mno")
+ with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"):
+ await s2.send(b"mno", socket.MSG_MORE)
+
+ # sendto errors
+ # it's successfully used earlier
+ with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"):
+ await s2.sendto(b"mno", socket.MSG_MORE, s1.getsockname())
+ with pytest.raises(TypeError, match="wrong number of arguments"):
+ await s2.sendto(b"mno", socket.MSG_MORE, s1.getsockname(), "extra arg") # type: ignore[call-overload]
+
+ # sendmsg
+ with pytest.raises(OSError, match="Transport endpoint is not connected"):
+ await s2.sendmsg([b"mno"])
+
+ assert await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) == 3
+
+ assert await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) == 3
+ with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"):
+ await s2.sendmsg([b"mno"], (), socket.MSG_MORE, s1.getsockname())
+
+
async def test_basic_tcp() -> None:
fn()
with pytest.raises(NotImplementedError):
trio.socket.socket()
+
+
+async def test_not_implemented_functions() -> None:
+ fn()
+ s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+
+ # getsockopt
+ with pytest.raises(OSError, match="FakeNet doesn't implement getsockopt"):
+ s1.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)
+
+ # setsockopt
+ with pytest.raises(
+ NotImplementedError, match="FakeNet always has IPV6_V6ONLY=True"
+ ):
+ s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, False)
+ with pytest.raises(OSError, match="FakeNet doesn't implement setsockopt"):
+ s1.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, True)
+ with pytest.raises(OSError, match="FakeNet doesn't implement setsockopt"):
+ s1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ # set_inheritable
+ s1.set_inheritable(False)
+ with pytest.raises(
+ NotImplementedError, match="FakeNet can't make inheritable sockets"
+ ):
+ s1.set_inheritable(True)
+
+ # get_inheritable
+ assert not s1.get_inheritable()
+
+
+async def test_getpeername() -> None:
+ fn()
+ s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ with pytest.raises(OSError, match="Transport endpoint is not connected") as exc:
+ s1.getpeername()
+ assert exc.value.errno == errno.ENOTCONN
+
+ await s1.bind(("127.0.0.1", 0))
+
+ with pytest.raises(
+ AssertionError,
+ match="This method seems to assume that self._binding has a remote UDPEndpoint",
+ ):
+ s1.getpeername()
+
+
+async def test_init() -> None:
+ fn()
+ with pytest.raises(
+ NotImplementedError,
+ match=re.escape(
+ f"FakeNet doesn't (yet) support type={trio.socket.SOCK_STREAM}"
+ ),
+ ):
+ s1 = trio.socket.socket()
+
+ # getsockname on unbound ipv4 socket
+ s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ assert s1.getsockname() == ("0.0.0.0", 0)
+
+ # getsockname on bound ipv4 socket
+ await s1.bind(("0.0.0.0", 0))
+ ip, port = s1.getsockname()
+ assert ip == "127.0.0.1"
+ assert port != 0
+
+ # getsockname on unbound ipv6 socket
+ s2 = trio.socket.socket(family=socket.AF_INET6, type=socket.SOCK_DGRAM)
+ assert s2.getsockname() == ("::", 0)
+
+ # getsockname on bound ipv6 socket
+ await s2.bind(("::", 0))
+ ip, port, *_ = s2.getsockname()
+ assert ip == "::1"
+ assert port != 0
+ assert _ == [0, 0]
diff --git a/trio/testing/_fake_net.py b/trio/testing/_fake_net.py
index fc9c0b361b..1ba0874853 100644
--- a/trio/testing/_fake_net.py
+++ b/trio/testing/_fake_net.py
@@ -53,12 +53,13 @@ def _wildcard_ip_for(family: int) -> IPAddress:
raise NotImplementedError("Unhandled ip address family") # pragma: no cover
-def _localhost_ip_for(family: int) -> IPAddress:
+# not used anywhere
+def _localhost_ip_for(family: int) -> IPAddress: # pragma: no cover
if family == trio.socket.AF_INET:
return ipaddress.ip_address("127.0.0.1")
elif family == trio.socket.AF_INET6:
return ipaddress.ip_address("::1")
- raise NotImplementedError("Unhandled ip address family") # pragma: no cover
+ raise NotImplementedError("Unhandled ip address family")
def _fake_err(code: int) -> NoReturn:
@@ -67,12 +68,12 @@ def _fake_err(code: int) -> NoReturn:
def _scatter(data: bytes, buffers: Iterable[Buffer]) -> int:
written = 0
- for buf in buffers:
+ for buf in buffers: # pragma: no branch
next_piece = data[written : written + memoryview(buf).nbytes]
with memoryview(buf) as mbuf:
mbuf[: len(next_piece)] = next_piece
written += len(next_piece)
- if written == len(data):
+ if written == len(data): # pragma: no branch
break
return written
@@ -114,7 +115,8 @@ class UDPPacket:
destination: UDPEndpoint
payload: bytes = attr.ib(repr=lambda p: p.hex())
- def reply(self, payload: bytes) -> UDPPacket:
+ # not used/tested anywhere
+ def reply(self, payload: bytes) -> UDPPacket: # pragma: no cover
return UDPPacket(
source=self.destination, destination=self.source, payload=payload
)
@@ -161,8 +163,8 @@ async def getnameinfo(
class FakeNet:
def __init__(self) -> None:
# When we need to pick an arbitrary unique ip address/port, use these:
- self._auto_ipv4_iter = ipaddress.IPv4Network("1.0.0.0/8").hosts()
- self._auto_ipv4_iter = ipaddress.IPv6Network("1::/16").hosts() # type: ignore[assignment]
+ self._auto_ipv4_iter = ipaddress.IPv4Network("1.0.0.0/8").hosts() # untested
+ self._auto_ipv6_iter = ipaddress.IPv6Network("1::/16").hosts() # untested
self._auto_port_iter = iter(range(50000, 65535))
self._bound: dict[UDPBinding, FakeSocket] = {}
@@ -200,9 +202,9 @@ def __init__(
):
self._fake_net = fake_net
- if not family:
+ if not family: # pragma: no cover
family = trio.socket.AF_INET
- if not type:
+ if not type: # pragma: no cover
type = trio.socket.SOCK_STREAM
if family not in (trio.socket.AF_INET, trio.socket.AF_INET6):
@@ -240,7 +242,6 @@ def _check_closed(self) -> None:
_fake_err(errno.EBADF)
def close(self) -> None:
- # breakpoint()
if self._closed:
return
self._closed = True
@@ -274,7 +275,9 @@ async def bind(self, addr: object) -> None:
if self._binding is not None:
_fake_err(errno.EINVAL)
await trio.lowlevel.checkpoint()
- ip_str, port = await self._resolve_address_nocp(addr, local=True)
+ ip_str, port, *_ = await self._resolve_address_nocp(addr, local=True)
+ assert _ == [], "TODO: handle other values?"
+
ip = ipaddress.ip_address(ip_str)
assert _family_for(ip) == self.family
# We convert binds to INET_ANY into binds to localhost
@@ -291,25 +294,14 @@ async def bind(self, addr: object) -> None:
async def connect(self, peer: object) -> NoReturn:
raise NotImplementedError("FakeNet does not (yet) support connected sockets")
- async def sendmsg(self, *args: Any) -> int:
+ async def sendmsg(
+ self,
+ buffers: Iterable[Buffer],
+ ancdata: Iterable[tuple[int, int, Buffer]] = (),
+ flags: int = 0,
+ address: Any | None = None,
+ ) -> int:
self._check_closed()
- ancdata = []
- flags = 0
- address = None
-
- # This does *not* match up with socket.socket.sendmsg (!!!)
- # https://docs.python.org/3/library/socket.html#socket.socket.sendmsg
- # they always have (buffers, ancdata, flags, address)
- if len(args) == 1:
- (buffers,) = args
- elif len(args) == 2:
- buffers, address = args
- elif len(args) == 3:
- buffers, flags, address = args
- elif len(args) == 4:
- buffers, ancdata, flags, address = args
- else:
- raise TypeError("wrong number of arguments")
await trio.lowlevel.checkpoint()
@@ -351,6 +343,14 @@ async def recvmsg_into(
raise NotImplementedError("FakeNet doesn't support ancillary data")
if flags != 0:
raise NotImplementedError("FakeNet doesn't support any recv flags")
+ if self._binding is None:
+ # I messed this up a few times when writing tests ... but it also never happens
+ # in any of the existing tests, so maybe it could be intentional...
+ raise NotImplementedError(
+ "The code will most likely hang if you try to receive on a fakesocket "
+ "without a binding. If that is not the case, or you explicitly want to "
+ "test that, remove this warning."
+ )
self._check_closed()
@@ -385,7 +385,7 @@ def getpeername(self) -> tuple[str, int] | tuple[str, int, int, int]:
assert hasattr(
self._binding, "remote"
), "This method seems to assume that self._binding has a remote UDPEndpoint"
- if self._binding.remote is not None:
+ if self._binding.remote is not None: # pragma: no cover
assert isinstance(
self._binding.remote, UDPEndpoint
), "Self._binding.remote should be a UDPEndpoint"
@@ -450,7 +450,25 @@ def __exit__(
async def send(self, data: Buffer, flags: int = 0) -> int:
return await self.sendto(data, flags, None)
+ @overload
+ async def sendto(
+ self, __data: Buffer, __address: tuple[object, ...] | str | Buffer
+ ) -> int:
+ ...
+
+ @overload
+ async def sendto(
+ self,
+ __data: Buffer,
+ __flags: int,
+ __address: tuple[object, ...] | str | None | Buffer,
+ ) -> int:
+ ...
+
async def sendto(self, *args: Any) -> int:
+ data: Buffer
+ flags: int
+ address: tuple[object, ...] | str | Buffer
if len(args) == 2:
data, address = args
flags = 0
From 40393caefb060f13b21b3e920f9779793d05639f Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 3 Nov 2023 15:43:08 +0100
Subject: [PATCH 2/5] account for platform differences ... might scrap this if
it breaks too much of dtls tests
---
trio/_tests/test_fakenet.py | 126 +++++++++++++++++++++++++-----------
trio/testing/_fake_net.py | 39 ++++++++---
2 files changed, 119 insertions(+), 46 deletions(-)
diff --git a/trio/_tests/test_fakenet.py b/trio/_tests/test_fakenet.py
index cbb6a5c431..a9dfe9f772 100644
--- a/trio/_tests/test_fakenet.py
+++ b/trio/_tests/test_fakenet.py
@@ -1,12 +1,21 @@
import errno
import re
import socket
+import sys
import pytest
import trio
from trio.testing._fake_net import FakeNet
+# ENOTCONN gives different messages on different platforms
+if sys.platform == "linux":
+ ENOTCONN_MSG = "Transport endpoint is not connected"
+elif sys.platform == "darwin":
+ ENOTCONN_MSG = "Socket is not connected"
+else:
+ ENOTCONN_MSG = "Unknown error"
+
def fn() -> FakeNet:
fn = FakeNet()
@@ -97,52 +106,95 @@ async def test_recv_methods() -> None:
assert nbytes == 3
assert buf2 == b"jkl" + b"\x00" * 7
- # recvmsg_into
- assert await s1.sendto(b"xyzw", s2.getsockname()) == 4
- buf1 = bytearray(2)
- buf2 = bytearray(3)
- ret = await s2.recvmsg_into([buf1, buf2])
- (nbytes, ancdata, msg_flags, addr) = ret
- assert nbytes == 4
- assert buf1 == b"xy"
- assert buf2 == b"zw" + b"\x00"
- assert ancdata == []
- assert msg_flags == 0
- assert addr == s1.getsockname()
-
- # recvmsg_into with MSG_TRUNC set
- assert await s1.sendto(b"xyzwv", s2.getsockname()) == 5
- buf1 = bytearray(2)
- ret = await s2.recvmsg_into([buf1])
- (nbytes, ancdata, msg_flags, addr) = ret
- assert nbytes == 2
- assert buf1 == b"xy"
- assert ancdata == []
- assert msg_flags == socket.MSG_TRUNC
- assert addr == s1.getsockname()
+ if sys.platform == "linux":
+ flags = socket.MSG_MORE
+ else:
+ flags = 1
# Send seems explicitly non-functional
- with pytest.raises(OSError, match="Transport endpoint is not connected"):
+ with pytest.raises(OSError, match=ENOTCONN_MSG) as exc:
await s2.send(b"mno")
+ assert exc.value.errno == errno.ENOTCONN
with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"):
- await s2.send(b"mno", socket.MSG_MORE)
+ await s2.send(b"mno", flags)
# sendto errors
# it's successfully used earlier
with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"):
- await s2.sendto(b"mno", socket.MSG_MORE, s1.getsockname())
+ await s2.sendto(b"mno", flags, s1.getsockname())
with pytest.raises(TypeError, match="wrong number of arguments"):
- await s2.sendto(b"mno", socket.MSG_MORE, s1.getsockname(), "extra arg") # type: ignore[call-overload]
-
- # sendmsg
- with pytest.raises(OSError, match="Transport endpoint is not connected"):
- await s2.sendmsg([b"mno"])
-
- assert await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) == 3
-
- assert await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) == 3
- with pytest.raises(NotImplementedError, match="FakeNet send flags must be 0, not"):
- await s2.sendmsg([b"mno"], (), socket.MSG_MORE, s1.getsockname())
+ await s2.sendto(b"mno", flags, s1.getsockname(), "extra arg") # type: ignore[call-overload]
+
+
+@pytest.mark.skipif(sys.platform == "win32", "functions not in socket on windows")
+async def test_nonwindows_functionality() -> None:
+ if sys.platform != "win32":
+ fn()
+ s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ await s1.bind(("127.0.0.1", 0))
+
+ # sendmsg
+ with pytest.raises(OSError, match=ENOTCONN_MSG) as exc:
+ await s2.sendmsg([b"mno"])
+ assert exc.value.errno == errno.ENOTCONN
+
+ assert await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) == 3
+
+ # TODO: recvmsg
+
+ # recvmsg_into
+ assert await s1.sendto(b"xyzw", s2.getsockname()) == 4
+ buf1 = bytearray(2)
+ buf2 = bytearray(3)
+ ret = await s2.recvmsg_into([buf1, buf2])
+ (nbytes, ancdata, msg_flags, addr) = ret
+ assert nbytes == 4
+ assert buf1 == b"xy"
+ assert buf2 == b"zw" + b"\x00"
+ assert ancdata == []
+ assert msg_flags == 0
+ assert addr == s1.getsockname()
+
+ # recvmsg_into with MSG_TRUNC set
+ assert await s1.sendto(b"xyzwv", s2.getsockname()) == 5
+ buf1 = bytearray(2)
+ ret = await s2.recvmsg_into([buf1])
+ (nbytes, ancdata, msg_flags, addr) = ret
+ assert nbytes == 2
+ assert buf1 == b"xy"
+ assert ancdata == []
+ assert msg_flags == socket.MSG_TRUNC
+ assert addr == s1.getsockname()
+
+ with pytest.raises(
+ AttributeError, match="type object 'FakeSocket' has no attribute 'share'"
+ ):
+ await s1.share(0) # type: ignore[attr-defined]
+
+
+@pytest.mark.skipif(sys.platform != "win32", "windows-specific fakesocket testing")
+async def test_windows_functionality() -> None:
+ if sys.platform == "win32":
+ fn()
+ s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
+ await s1.bind(("127.0.0.1", 0))
+ with pytest.raises(
+ AttributeError, match="type object 'FakeSocket' has no attribute 'sendmsg'"
+ ):
+ await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) # type: ignore[attr-defined]
+ with pytest.raises(
+ AttributeError, match="type object 'FakeSocket' has no attribute 'recvmsg'"
+ ):
+ s2.recvmsg(0) # type: ignore[attr-defined]
+ with pytest.raises(
+ AttributeError,
+ match="type object 'FakeSocket' has no attribute 'recvmsg_into'",
+ ):
+ s2.recvmsg_into([]) # type: ignore[attr-defined]
+ with pytest.raises(NotImplementedError):
+ s1.share(0)
async def test_basic_tcp() -> None:
diff --git a/trio/testing/_fake_net.py b/trio/testing/_fake_net.py
index 1ba0874853..62b1108eab 100644
--- a/trio/testing/_fake_net.py
+++ b/trio/testing/_fake_net.py
@@ -13,6 +13,8 @@
import errno
import ipaddress
import os
+import socket
+import sys
from typing import (
TYPE_CHECKING,
Any,
@@ -294,7 +296,7 @@ async def bind(self, addr: object) -> None:
async def connect(self, peer: object) -> NoReturn:
raise NotImplementedError("FakeNet does not (yet) support connected sockets")
- async def sendmsg(
+ async def _sendmsg(
self,
buffers: Iterable[Buffer],
ancdata: Iterable[tuple[int, int, Buffer]] = (),
@@ -333,7 +335,12 @@ async def sendmsg(
return len(payload)
- async def recvmsg_into(
+ if sys.platform != "win32" or (
+ not TYPE_CHECKING and hasattr(socket.socket, "sendmsg")
+ ):
+ sendmsg = _sendmsg
+
+ async def _recvmsg_into(
self,
buffers: Iterable[Buffer],
ancbufsize: int = 0,
@@ -364,6 +371,11 @@ async def recvmsg_into(
msg_flags |= trio.socket.MSG_TRUNC
return written, ancdata, msg_flags, address
+ if sys.platform != "win32" or (
+ not TYPE_CHECKING and hasattr(socket.socket, "sendmsg")
+ ):
+ recvmsg_into = _recvmsg_into
+
################################################################
# Simple state query stuff
################################################################
@@ -476,7 +488,7 @@ async def sendto(self, *args: Any) -> int:
data, flags, address = args
else:
raise TypeError("wrong number of arguments")
- return await self.sendmsg([data], [], flags, address)
+ return await self._sendmsg([data], [], flags, address)
async def recv(self, bufsize: int, flags: int = 0) -> bytes:
data, address = await self.recvfrom(bufsize, flags)
@@ -487,7 +499,7 @@ async def recv_into(self, buf: Buffer, nbytes: int = 0, flags: int = 0) -> int:
return got_bytes
async def recvfrom(self, bufsize: int, flags: int = 0) -> tuple[bytes, Any]:
- data, ancdata, msg_flags, address = await self.recvmsg(bufsize, flags)
+ data, ancdata, msg_flags, address = await self._recvmsg(bufsize, flags)
return data, address
async def recvfrom_into(
@@ -495,20 +507,25 @@ async def recvfrom_into(
) -> tuple[int, Any]:
if nbytes != 0 and nbytes != memoryview(buf).nbytes:
raise NotImplementedError("partial recvfrom_into")
- got_nbytes, ancdata, msg_flags, address = await self.recvmsg_into(
+ got_nbytes, ancdata, msg_flags, address = await self._recvmsg_into(
[buf], 0, flags
)
return got_nbytes, address
- async def recvmsg(
+ async def _recvmsg(
self, bufsize: int, ancbufsize: int = 0, flags: int = 0
) -> tuple[bytes, list[tuple[int, int, bytes]], int, Any]:
buf = bytearray(bufsize)
- got_nbytes, ancdata, msg_flags, address = await self.recvmsg_into(
+ got_nbytes, ancdata, msg_flags, address = await self._recvmsg_into(
[buf], ancbufsize, flags
)
return (bytes(buf[:got_nbytes]), ancdata, msg_flags, address)
+ if sys.platform != "win32" or (
+ not TYPE_CHECKING and hasattr(socket.socket, "sendmsg")
+ ):
+ recvmsg = _recvmsg
+
def fileno(self) -> int:
raise NotImplementedError("can't get fileno() for FakeNet sockets")
@@ -522,5 +539,9 @@ def set_inheritable(self, inheritable: bool) -> None:
if inheritable:
raise NotImplementedError("FakeNet can't make inheritable sockets")
- def share(self, process_id: int) -> bytes:
- raise NotImplementedError("FakeNet can't share sockets")
+ if sys.platform == "win32" or (
+ not TYPE_CHECKING and hasattr(socket.socket, "share")
+ ):
+
+ def share(self, process_id: int) -> bytes:
+ raise NotImplementedError("FakeNet can't share sockets")
From 25dda1600d2be2015c53be76732ac6d08fe2abec Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 3 Nov 2023 16:08:41 +0100
Subject: [PATCH 3/5] fix tests
---
trio/_tests/test_fakenet.py | 23 ++++++++++++++++-------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/trio/_tests/test_fakenet.py b/trio/_tests/test_fakenet.py
index a9dfe9f772..eac997356e 100644
--- a/trio/_tests/test_fakenet.py
+++ b/trio/_tests/test_fakenet.py
@@ -126,13 +126,15 @@ async def test_recv_methods() -> None:
await s2.sendto(b"mno", flags, s1.getsockname(), "extra arg") # type: ignore[call-overload]
-@pytest.mark.skipif(sys.platform == "win32", "functions not in socket on windows")
+@pytest.mark.skipif(
+ sys.platform == "win32", reason="functions not in socket on windows"
+)
async def test_nonwindows_functionality() -> None:
if sys.platform != "win32":
fn()
s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
- await s1.bind(("127.0.0.1", 0))
+ await s2.bind(("127.0.0.1", 0))
# sendmsg
with pytest.raises(OSError, match=ENOTCONN_MSG) as exc:
@@ -140,6 +142,11 @@ async def test_nonwindows_functionality() -> None:
assert exc.value.errno == errno.ENOTCONN
assert await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) == 3
+ (data, ancdata, msg_flags, addr) = await s2.recvmsg(10)
+ assert data == b"jkl"
+ assert ancdata == []
+ assert msg_flags == 0
+ assert addr == s1.getsockname()
# TODO: recvmsg
@@ -168,12 +175,14 @@ async def test_nonwindows_functionality() -> None:
assert addr == s1.getsockname()
with pytest.raises(
- AttributeError, match="type object 'FakeSocket' has no attribute 'share'"
+ AttributeError, match="'FakeSocket' object has no attribute 'share'"
):
await s1.share(0) # type: ignore[attr-defined]
-@pytest.mark.skipif(sys.platform != "win32", "windows-specific fakesocket testing")
+@pytest.mark.skipif(
+ sys.platform != "win32", reason="windows-specific fakesocket testing"
+)
async def test_windows_functionality() -> None:
if sys.platform == "win32":
fn()
@@ -181,16 +190,16 @@ async def test_windows_functionality() -> None:
s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
await s1.bind(("127.0.0.1", 0))
with pytest.raises(
- AttributeError, match="type object 'FakeSocket' has no attribute 'sendmsg'"
+ AttributeError, match="'FakeSocket' object has no attribute 'sendmsg'"
):
await s1.sendmsg([b"jkl"], (), 0, s2.getsockname()) # type: ignore[attr-defined]
with pytest.raises(
- AttributeError, match="type object 'FakeSocket' has no attribute 'recvmsg'"
+ AttributeError, match="'FakeSocket' object has no attribute 'recvmsg'"
):
s2.recvmsg(0) # type: ignore[attr-defined]
with pytest.raises(
AttributeError,
- match="type object 'FakeSocket' has no attribute 'recvmsg_into'",
+ match="'FakeSocket' object has no attribute 'recvmsg_into'",
):
s2.recvmsg_into([]) # type: ignore[attr-defined]
with pytest.raises(NotImplementedError):
From f557b97286097b6241a341f6acd58d71dc81e047 Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 3 Nov 2023 18:30:24 +0100
Subject: [PATCH 4/5] more minor test fixes
---
trio/_tests/test_fakenet.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/trio/_tests/test_fakenet.py b/trio/_tests/test_fakenet.py
index eac997356e..1055ebd6e7 100644
--- a/trio/_tests/test_fakenet.py
+++ b/trio/_tests/test_fakenet.py
@@ -106,8 +106,8 @@ async def test_recv_methods() -> None:
assert nbytes == 3
assert buf2 == b"jkl" + b"\x00" * 7
- if sys.platform == "linux":
- flags = socket.MSG_MORE
+ if sys.platform == "linux" and sys.implementation.name == "cpython":
+ flags: int = socket.MSG_MORE
else:
flags = 1
@@ -244,7 +244,7 @@ async def test_not_implemented_functions() -> None:
async def test_getpeername() -> None:
fn()
s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
- with pytest.raises(OSError, match="Transport endpoint is not connected") as exc:
+ with pytest.raises(OSError, match=ENOTCONN_MSG) as exc:
s1.getpeername()
assert exc.value.errno == errno.ENOTCONN
From ba4764eb112ac3e85c36a2d5b26b9ef2fda1b93f Mon Sep 17 00:00:00 2001
From: jakkdl
Date: Fri, 3 Nov 2023 18:38:13 +0100
Subject: [PATCH 5/5] add two pragma: no branch to hit 100% patch coverage
---
trio/_tests/test_fakenet.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/trio/_tests/test_fakenet.py b/trio/_tests/test_fakenet.py
index 1055ebd6e7..f7f5c16b4d 100644
--- a/trio/_tests/test_fakenet.py
+++ b/trio/_tests/test_fakenet.py
@@ -130,7 +130,8 @@ async def test_recv_methods() -> None:
sys.platform == "win32", reason="functions not in socket on windows"
)
async def test_nonwindows_functionality() -> None:
- if sys.platform != "win32":
+ # mypy doesn't support a good way of aborting typechecking on different platforms
+ if sys.platform != "win32": # pragma: no branch
fn()
s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
@@ -184,7 +185,8 @@ async def test_nonwindows_functionality() -> None:
sys.platform != "win32", reason="windows-specific fakesocket testing"
)
async def test_windows_functionality() -> None:
- if sys.platform == "win32":
+ # mypy doesn't support a good way of aborting typechecking on different platforms
+ if sys.platform == "win32": # pragma: no branch
fn()
s1 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)
s2 = trio.socket.socket(type=trio.socket.SOCK_DGRAM)