Skip to content

tests: get connect_nonblock_xfer.py test working again and simplify it #16156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions extmod/modlwip.c
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,12 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui

MICROPY_PY_LWIP_ENTER

u16_t available = tcp_sndbuf(socket->pcb.tcp);
// If the socket is still connecting then don't let data be written to it.
// Otherwise, get the number of available bytes in the output buffer.
u16_t available = 0;
if (socket->state != STATE_CONNECTING) {
available = tcp_sndbuf(socket->pcb.tcp);
}

if (available == 0) {
// Non-blocking socket
Expand All @@ -718,7 +723,8 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui
// If peer fully closed socket, we would have socket->state set to ERR_RST (connection
// reset) by error callback.
// Avoid sending too small packets, so wait until at least 16 bytes available
while (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16) {
while (socket->state == STATE_CONNECTING
|| (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16)) {
MICROPY_PY_LWIP_EXIT
if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) {
*_errno = MP_ETIMEDOUT;
Expand Down Expand Up @@ -1548,7 +1554,7 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_
// raw socket is writable
ret |= MP_STREAM_POLL_WR;
#endif
} else if (socket->pcb.tcp != NULL && tcp_sndbuf(socket->pcb.tcp) > 0) {
} else if (socket->state != STATE_CONNECTING && socket->pcb.tcp != NULL && tcp_sndbuf(socket->pcb.tcp) > 0) {
// TCP socket is writable
// Note: pcb.tcp==NULL if state<0, and in this case we can't call tcp_sndbuf
ret |= MP_STREAM_POLL_WR;
Expand Down
130 changes: 50 additions & 80 deletions tests/net_hosted/connect_nonblock_xfer.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
# test that socket.connect() on a non-blocking socket raises EINPROGRESS
# and that an immediate write/send/read/recv does the right thing

import sys, time, socket, errno, ssl
import errno
import select
import socket
import ssl

isMP = sys.implementation.name == "micropython"
# only mbedTLS supports non-blocking mode
if not hasattr(ssl, "MBEDTLS_VERSION"):
print("SKIP")
raise SystemExit


def dp(e):
# uncomment next line for development and testing, to print the actual exceptions
# print(repr(e))
pass
# get the name of an errno error code
def errno_name(er):
if er == errno.EAGAIN:
return "EAGAIN"
if er == errno.EINPROGRESS:
return "EINPROGRESS"
return er


# do_connect establishes the socket and wraps it if tls is True.
Expand All @@ -22,112 +31,75 @@ def do_connect(peer_addr, tls, handshake):
# print("Connecting to", peer_addr)
s.connect(peer_addr)
except OSError as er:
print("connect:", er.errno == errno.EINPROGRESS)
if er.errno != errno.EINPROGRESS:
print(" got", er.errno)
print("connect:", errno_name(er.errno))
# wrap with ssl/tls if desired
if tls:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
if hasattr(ssl_context, "check_hostname"):
ssl_context.check_hostname = False

try:
s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake)
print("wrap: True")
print("wrap ok: True")
except Exception as e:
dp(e)
print("wrap:", e)
elif handshake:
# just sleep a little bit, this allows any connect() errors to happen
time.sleep(0.2)
print("wrap er:", e)
return s


# poll a socket and print out the result
def poll(s):
poller = select.poll()
poller.register(s)
print("poll: ", poller.poll(0))


# test runs the test against a specific peer address.
def test(peer_addr, tls=False, handshake=False):
# MicroPython plain sockets have read/write, but CPython's don't
# MicroPython TLS sockets and CPython's have read/write
# hasRW captures this wonderful state of affairs
hasRW = isMP or tls
def test(peer_addr, tls, handshake):
# MicroPython plain and TLS sockets have read/write
hasRW = True

# MicroPython plain sockets and CPython's have send/recv
# MicroPython TLS sockets don't have send/recv, but CPython's do
# hasSR captures this wonderful state of affairs
hasSR = not (isMP and tls)
# MicroPython plain sockets have send/recv
# MicroPython TLS sockets don't have send/recv
hasSR = not tls

# connect + send
# non-blocking send should raise EAGAIN
if hasSR:
s = do_connect(peer_addr, tls, handshake)
# send -> 4 or EAGAIN
poll(s)
try:
ret = s.send(b"1234")
print("send:", handshake and ret == 4)
print("send ok:", ret) # shouldn't get here
except OSError as er:
#
dp(er)
print("send:", er.errno in (errno.EAGAIN, errno.EINPROGRESS))
print("send er:", errno_name(er.errno))
s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("send:", True)

# connect + write
# non-blocking write should return None
if hasRW:
s = do_connect(peer_addr, tls, handshake)
# write -> None
try:
ret = s.write(b"1234")
print("write:", ret in (4, None)) # SSL may accept 4 into buffer
except OSError as er:
dp(er)
print("write:", False) # should not raise
except ValueError as er: # CPython
dp(er)
print("write:", er.args[0] == "Write on closed or unwrapped SSL socket.")
poll(s)
ret = s.write(b"1234")
print("write: ", ret)
s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("write:", True)

# connect + recv
# non-blocking recv should raise EAGAIN
if hasSR:
# connect + recv
s = do_connect(peer_addr, tls, handshake)
# recv -> EAGAIN
poll(s)
try:
print("recv:", s.recv(10))
ret = s.recv(10)
print("recv ok:", ret) # shouldn't get here
except OSError as er:
dp(er)
print("recv:", er.errno == errno.EAGAIN)
print("recv er:", errno_name(er.errno))
s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("recv:", True)

# connect + read
# non-blocking read should return None
if hasRW:
s = do_connect(peer_addr, tls, handshake)
# read -> None
try:
ret = s.read(10)
print("read:", ret is None)
except OSError as er:
dp(er)
print("read:", False) # should not raise
except ValueError as er: # CPython
dp(er)
print("read:", er.args[0] == "Read on closed or unwrapped SSL socket.")
poll(s)
ret = s.read(10)
print("read: ", ret)
s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("read:", True)


if __name__ == "__main__":
Expand All @@ -136,10 +108,8 @@ def test(peer_addr, tls=False, handshake=False):
print("--- Plain sockets to nowhere ---")
test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False)
print("--- SSL sockets to nowhere ---")
# this test fails with AXTLS because do_handshake=False blocks on first read/write and
# there it times out until the connect is aborted
test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False)
print("--- Plain sockets ---")
test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, True)
test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, False)
print("--- SSL sockets ---")
test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True)
44 changes: 44 additions & 0 deletions tests/net_hosted/connect_nonblock_xfer.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--- Plain sockets to nowhere ---
connect: EINPROGRESS
poll: []
send er: EAGAIN
connect: EINPROGRESS
poll: []
write: None
connect: EINPROGRESS
poll: []
recv er: EAGAIN
connect: EINPROGRESS
poll: []
read: None
--- SSL sockets to nowhere ---
connect: EINPROGRESS
wrap ok: True
poll: []
write: None
connect: EINPROGRESS
wrap ok: True
poll: []
read: None
--- Plain sockets ---
connect: EINPROGRESS
poll: []
send er: EAGAIN
connect: EINPROGRESS
poll: []
write: None
connect: EINPROGRESS
poll: []
recv er: EAGAIN
connect: EINPROGRESS
poll: []
read: None
--- SSL sockets ---
connect: EINPROGRESS
wrap ok: True
poll: [(<SSLSocket>, 4)]
write: 4
connect: EINPROGRESS
wrap ok: True
poll: [(<SSLSocket>, 4)]
read: None
Loading