tests/net_hosted: Improve and simplify non-block-xfer test.

CPython changed its non-blocking socket behaviour recently and this test
would not run under CPython anymore.  So the following steps were taken to
get the test working again and then simplify it:

- Run the test against CPython 3.10.10 and capture the output into the .exp
  file for the test.

- Run this test on unix port of MicroPython and verify that the output
  matches the CPython 3.10.10 output in the new .exp file (it did).  From
  now on take unix MicroPython as the source of truth for this test when
  modifying it.

- Remove all code that was there for CPython compatibility.

- Make it print out more useful information during the test run, including
  names of the OSError errno values.

- Add polling of the socket before the send/write/recv/read to verify that
  the poll gives the correct result in non-blocking mode.

Tested on unix MicroPython, ESP32_GENERIC, PYBD_SF2 and RPI_PICO_W boards.

Signed-off-by: Damien George <damien@micropython.org>
This commit is contained in:
Damien George
2024-11-05 11:19:45 +11:00
parent eab2869990
commit 69023622ee
2 changed files with 95 additions and 81 deletions

View File

@@ -1,15 +1,24 @@
# test that socket.connect() on a non-blocking socket raises EINPROGRESS # test that socket.connect() on a non-blocking socket raises EINPROGRESS
# and that an immediate write/send/read/recv does the right thing # 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): # get the name of an errno error code
# uncomment next line for development and testing, to print the actual exceptions def errno_name(er):
# print(repr(e)) if er == errno.EAGAIN:
pass return "EAGAIN"
if er == errno.EINPROGRESS:
return "EINPROGRESS"
return er
# do_connect establishes the socket and wraps it if tls is True. # do_connect establishes the socket and wraps it if tls is True.
@@ -22,112 +31,75 @@ def do_connect(peer_addr, tls, handshake):
# print("Connecting to", peer_addr) # print("Connecting to", peer_addr)
s.connect(peer_addr) s.connect(peer_addr)
except OSError as er: except OSError as er:
print("connect:", er.errno == errno.EINPROGRESS) print("connect:", errno_name(er.errno))
if er.errno != errno.EINPROGRESS:
print(" got", er.errno)
# wrap with ssl/tls if desired # wrap with ssl/tls if desired
if tls: if tls:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
if hasattr(ssl_context, "check_hostname"):
ssl_context.check_hostname = False
try: try:
s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake) s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake)
print("wrap: True") print("wrap ok: True")
except Exception as e: except Exception as e:
dp(e) print("wrap er:", e)
print("wrap:", e)
elif handshake:
# just sleep a little bit, this allows any connect() errors to happen
time.sleep(0.2)
return s return s
# test runs the test against a specific peer address. # poll a socket and print out the result
def test(peer_addr, tls=False, handshake=False): def poll(s):
# MicroPython plain sockets have read/write, but CPython's don't poller = select.poll()
# MicroPython TLS sockets and CPython's have read/write poller.register(s)
# hasRW captures this wonderful state of affairs print("poll: ", poller.poll(0))
hasRW = isMP or tls
# MicroPython plain sockets and CPython's have send/recv
# MicroPython TLS sockets don't have send/recv, but CPython's do # test runs the test against a specific peer address.
# hasSR captures this wonderful state of affairs def test(peer_addr, tls, handshake):
hasSR = not (isMP and tls) # MicroPython plain and TLS sockets have read/write
hasRW = True
# MicroPython plain sockets have send/recv
# MicroPython TLS sockets don't have send/recv
hasSR = not tls
# connect + send # connect + send
# non-blocking send should raise EAGAIN
if hasSR: if hasSR:
s = do_connect(peer_addr, tls, handshake) s = do_connect(peer_addr, tls, handshake)
# send -> 4 or EAGAIN poll(s)
try: try:
ret = s.send(b"1234") ret = s.send(b"1234")
print("send:", handshake and ret == 4) print("send ok:", ret) # shouldn't get here
except OSError as er: except OSError as er:
# print("send er:", errno_name(er.errno))
dp(er)
print("send:", er.errno in (errno.EAGAIN, errno.EINPROGRESS))
s.close() s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("send:", True)
# connect + write # connect + write
# non-blocking write should return None
if hasRW: if hasRW:
s = do_connect(peer_addr, tls, handshake) s = do_connect(peer_addr, tls, handshake)
# write -> None poll(s)
try: ret = s.write(b"1234")
ret = s.write(b"1234") print("write: ", ret)
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.")
s.close() 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: if hasSR:
# connect + recv
s = do_connect(peer_addr, tls, handshake) s = do_connect(peer_addr, tls, handshake)
# recv -> EAGAIN poll(s)
try: try:
print("recv:", s.recv(10)) ret = s.recv(10)
print("recv ok:", ret) # shouldn't get here
except OSError as er: except OSError as er:
dp(er) print("recv er:", errno_name(er.errno))
print("recv:", er.errno == errno.EAGAIN)
s.close() s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("recv:", True)
# connect + read # connect + read
# non-blocking read should return None
if hasRW: if hasRW:
s = do_connect(peer_addr, tls, handshake) s = do_connect(peer_addr, tls, handshake)
# read -> None poll(s)
try: ret = s.read(10)
ret = s.read(10) print("read: ", ret)
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.")
s.close() s.close()
else: # fake it...
print("connect:", True)
if tls:
print("wrap:", True)
print("read:", True)
if __name__ == "__main__": if __name__ == "__main__":
@@ -136,10 +108,8 @@ if __name__ == "__main__":
print("--- Plain sockets to nowhere ---") print("--- Plain sockets to nowhere ---")
test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False) test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False)
print("--- SSL sockets to nowhere ---") 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) test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False)
print("--- Plain sockets ---") 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 ---") print("--- SSL sockets ---")
test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True) test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True)

View File

@@ -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