From 159f13ad941208b82ec95559ec398581926a35d8 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 14 Jul 2025 16:22:04 +0200 Subject: [PATCH 1/6] Adding `socket` methods used by `trio`, not 100% sure about the actual implementation. --- mocket/socket.py | 80 ++++++++++++++++++++++++++++++++++++++++++++ tests/test_socket.py | 33 ++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/mocket/socket.py b/mocket/socket.py index 41b25bc..f7bfc1a 100644 --- a/mocket/socket.py +++ b/mocket/socket.py @@ -191,6 +191,74 @@ def sendall(self, data, entry=None, *args, **kwargs): self.io.truncate() self.io.seek(0) + def sendmsg( + self, + buffers: list[ReadableBuffer], + ancdata: list[tuple[int, bytes]] | None = None, + flags: int = 0, + address: Address | None = None, + ) -> int: + if not buffers: + return 0 + + data = b"".join(buffers) + self.sendall(data) + return len(data) + + def recvmsg( + self, + buffersize: int | None = None, + ancbufsize: int | None = None, + flags: int = 0, + ) -> tuple[bytes, list[tuple[int, bytes]]]: + """ + Receive a message from the socket. + This is a mock implementation that reads from the MocketSocketIO. + """ + data = self.recv(buffersize) + if not data: + return b"", [] + + # Mocking the ancillary data and flags as empty + return data, [] + + def recvmsg_into( + self, + buffers: list[ReadableBuffer], + ancbufsize: int | None = None, + flags: int = 0, + address: Address | None = None, + ): + """ + Receive a message into multiple buffers. + This is a mock implementation that reads from the MocketSocketIO. + """ + if not buffers: + return 0 + + data = self.recv(len(buffers[0])) + if not data: + return 0 + + for i, buffer in enumerate(buffers): + if i < len(data): + buffer[: len(data)] = data + else: + buffer[:] = b"" + return len(data) + + def recvfrom_into( + self, + buffer: WriteableBuffer, + buffersize: int | None = None, + flags: int | None = None, + ): + """ + Receive data into a buffer and return the number of bytes received. + This is a mock implementation that reads from the MocketSocketIO. + """ + return self.recv_into(buffer, buffersize, flags) + def recv_into( self, buffer: WriteableBuffer, @@ -286,6 +354,18 @@ def send( self._entry = entry return len(data) + def accept(self) -> tuple[MocketSocket, _RetAddress]: + """Accept a connection and return a new MocketSocket object.""" + new_socket = MocketSocket( + family=self._family, + type=self._type, + proto=self._proto, + ) + new_socket._address = (self._host, self._port) + new_socket._host = self._host + new_socket._port = self._port + return new_socket, (self._host, self._port) + def close(self) -> None: if self._true_socket and not self._true_socket._closed: self._true_socket.close() diff --git a/tests/test_socket.py b/tests/test_socket.py index 4c362f5..4f0bc6b 100644 --- a/tests/test_socket.py +++ b/tests/test_socket.py @@ -30,3 +30,36 @@ def test_udp_socket(): assert data == response_data assert address == (host, port) + + +def test_recvmsg(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + test_data = b"hello world" + sock._io = type("MockIO", (), {"read": lambda self, n: test_data})() + data, ancdata = sock.recvmsg(1024) + assert data == test_data + assert ancdata == [] + + +def test_recvmsg_into(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + test_data = b"foobar" + sock._io = type("MockIO", (), {"read": lambda self, n: test_data})() + buf = bytearray(10) + buf2 = bytearray(10) + buffers = [buf, buf2] + nbytes = sock.recvmsg_into(buffers) + assert nbytes == len(test_data) + assert buf[: len(test_data)] == test_data + + +def test_accept(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + sock._host = "127.0.0.1" + sock._port = 8080 + new_sock, addr = sock.accept() + assert isinstance(new_sock, MocketSocket) + assert new_sock is not sock + assert addr == ("127.0.0.1", 8080) + assert new_sock._host == "127.0.0.1" + assert new_sock._port == 8080 From 1ec3d560c280e1643afa965d35973e06decb85d3 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 14 Jul 2025 16:24:57 +0200 Subject: [PATCH 2/6] Bump version. --- mocket/__init__.py | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mocket/__init__.py b/mocket/__init__.py index 79955f6..eaf33df 100644 --- a/mocket/__init__.py +++ b/mocket/__init__.py @@ -31,4 +31,4 @@ "FakeSSLContext", ) -__version__ = "3.13.9" +__version__ = "3.13.10" diff --git a/pyproject.toml b/pyproject.toml index 66b394b..a921e22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,6 +61,7 @@ test = [ "mypy", "types-decorator", "types-requests", + "trio", ] speedups = [ "xxhash;platform_python_implementation=='CPython'", From 18e88509f76ab1c0a39549782fa786b806e2fc06 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 14 Jul 2025 16:51:46 +0200 Subject: [PATCH 3/6] Add test. --- mocket/socket.py | 10 ++++++++-- tests/test_socket.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/mocket/socket.py b/mocket/socket.py index f7bfc1a..8bd7576 100644 --- a/mocket/socket.py +++ b/mocket/socket.py @@ -201,7 +201,7 @@ def sendmsg( if not buffers: return 0 - data = b"".join(buffers) + data = b"".join(bytes(b) for b in buffers) self.sendall(data) return len(data) @@ -308,7 +308,13 @@ def true_sendall(self, data: bytes, *args: Any, **kwargs: Any) -> bytes: if record is not None: return record.response - host, port = self._address + try: + host, port = self._address + except TypeError: + host = self._host + port = self._port + self._address = (host, port) + host = true_gethostbyname(host) with contextlib.suppress(OSError, ValueError): diff --git a/tests/test_socket.py b/tests/test_socket.py index 4f0bc6b..d8bd2d4 100644 --- a/tests/test_socket.py +++ b/tests/test_socket.py @@ -63,3 +63,18 @@ def test_accept(): assert addr == ("127.0.0.1", 8080) assert new_sock._host == "127.0.0.1" assert new_sock._port == 8080 + + +@mocketize +def test_sendmsg(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + sock._host = "127.0.0.1" + sock._port = 8080 + response_data = b"pong" + + Mocket.register(MocketEntry((sock._host, sock._port), [response_data])) + + msg = [b"foo", b"bar", b"foobaz"] + total_sent = sock.sendmsg(msg) + assert total_sent == sum(len(m) for m in msg) + assert Mocket.last_request() == b"".join(msg) From ada652284c4005941d555afcde2b70b9ce2e98bf Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 14 Jul 2025 17:44:56 +0200 Subject: [PATCH 4/6] Revert. --- mocket/socket.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/mocket/socket.py b/mocket/socket.py index 8bd7576..c53e773 100644 --- a/mocket/socket.py +++ b/mocket/socket.py @@ -308,13 +308,7 @@ def true_sendall(self, data: bytes, *args: Any, **kwargs: Any) -> bytes: if record is not None: return record.response - try: - host, port = self._address - except TypeError: - host = self._host - port = self._port - self._address = (host, port) - + host, port = self._address host = true_gethostbyname(host) with contextlib.suppress(OSError, ValueError): From e9ef42aaed9530af8c059614a65ceb79ef2f82fa Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 14 Jul 2025 17:53:18 +0200 Subject: [PATCH 5/6] Tests for empty `buffers`. --- tests/test_socket.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_socket.py b/tests/test_socket.py index d8bd2d4..aabbd8b 100644 --- a/tests/test_socket.py +++ b/tests/test_socket.py @@ -53,6 +53,12 @@ def test_recvmsg_into(): assert buf[: len(test_data)] == test_data +def test_recvmsg_into_empty_buffers(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.recvmsg_into([]) + assert result == 0 + + def test_accept(): sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) sock._host = "127.0.0.1" @@ -78,3 +84,9 @@ def test_sendmsg(): total_sent = sock.sendmsg(msg) assert total_sent == sum(len(m) for m in msg) assert Mocket.last_request() == b"".join(msg) + + +def test_sendmsg_empty_buffers(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + result = sock.sendmsg([]) + assert result == 0 From a0be340e214ad805a8dd8b29286308ef8dc09324 Mon Sep 17 00:00:00 2001 From: Giorgio Salluzzo Date: Mon, 14 Jul 2025 18:12:15 +0200 Subject: [PATCH 6/6] 100% coverage. --- mocket/socket.py | 12 +++++++----- tests/test_socket.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/mocket/socket.py b/mocket/socket.py index c53e773..e06a1a8 100644 --- a/mocket/socket.py +++ b/mocket/socket.py @@ -215,8 +215,9 @@ def recvmsg( Receive a message from the socket. This is a mock implementation that reads from the MocketSocketIO. """ - data = self.recv(buffersize) - if not data: + try: + data = self.recv(buffersize) + except BlockingIOError: return b"", [] # Mocking the ancillary data and flags as empty @@ -236,8 +237,9 @@ def recvmsg_into( if not buffers: return 0 - data = self.recv(len(buffers[0])) - if not data: + try: + data = self.recv(len(buffers[0])) + except BlockingIOError: return 0 for i, buffer in enumerate(buffers): @@ -257,7 +259,7 @@ def recvfrom_into( Receive data into a buffer and return the number of bytes received. This is a mock implementation that reads from the MocketSocketIO. """ - return self.recv_into(buffer, buffersize, flags) + return self.recv_into(buffer, buffersize, flags), self._address def recv_into( self, diff --git a/tests/test_socket.py b/tests/test_socket.py index aabbd8b..dad62a3 100644 --- a/tests/test_socket.py +++ b/tests/test_socket.py @@ -90,3 +90,39 @@ def test_sendmsg_empty_buffers(): sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) result = sock.sendmsg([]) assert result == 0 + + +def test_recvmsg_no_data(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + # Mock _io.read to return empty bytes + sock._io = type("MockIO", (), {"read": lambda self, n: b""})() + data, ancdata = sock.recvmsg(1024) + assert data == b"" + assert ancdata == [] + + +def test_recvmsg_into_no_data(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + # Mock _io.read to return empty bytes + sock._io = type("MockIO", (), {"read": lambda self, n: b""})() + buf = bytearray(10) + nbytes = sock.recvmsg_into([buf]) + assert nbytes == 0 + assert buf == bytearray(10) + + +def test_getsockopt(): + # getsockopt is a static method, so we can call it directly + result = MocketSocket.getsockopt(0, 0) + assert result == socket.SOCK_STREAM + + +def test_recvfrom_into(): + sock = MocketSocket(socket.AF_INET, socket.SOCK_STREAM) + test_data = b"abc123" + sock._io = type("MockIO", (), {"read": lambda self, n: test_data})() + buf = bytearray(10) + nbytes, addr = sock.recvfrom_into(buf) + assert nbytes == len(test_data) + assert buf[:nbytes] == test_data + assert addr == sock._address