Skip to content

Commit f071f01

Browse files
gh-122133: Rework pure Python socketpair tests to avoid use of importlib.reload. (#122493)
Co-authored-by: Gregory P. Smith <[email protected]>
1 parent d01fd24 commit f071f01

File tree

2 files changed

+64
-77
lines changed

2 files changed

+64
-77
lines changed

Lib/socket.py

+58-63
Original file line numberDiff line numberDiff line change
@@ -592,16 +592,65 @@ def fromshare(info):
592592
return socket(0, 0, 0, info)
593593
__all__.append("fromshare")
594594

595-
if hasattr(_socket, "socketpair"):
595+
# Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain.
596+
# This is used if _socket doesn't natively provide socketpair. It's
597+
# always defined so that it can be patched in for testing purposes.
598+
def _fallback_socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
599+
if family == AF_INET:
600+
host = _LOCALHOST
601+
elif family == AF_INET6:
602+
host = _LOCALHOST_V6
603+
else:
604+
raise ValueError("Only AF_INET and AF_INET6 socket address families "
605+
"are supported")
606+
if type != SOCK_STREAM:
607+
raise ValueError("Only SOCK_STREAM socket type is supported")
608+
if proto != 0:
609+
raise ValueError("Only protocol zero is supported")
610+
611+
# We create a connected TCP socket. Note the trick with
612+
# setblocking(False) that prevents us from having to create a thread.
613+
lsock = socket(family, type, proto)
614+
try:
615+
lsock.bind((host, 0))
616+
lsock.listen()
617+
# On IPv6, ignore flow_info and scope_id
618+
addr, port = lsock.getsockname()[:2]
619+
csock = socket(family, type, proto)
620+
try:
621+
csock.setblocking(False)
622+
try:
623+
csock.connect((addr, port))
624+
except (BlockingIOError, InterruptedError):
625+
pass
626+
csock.setblocking(True)
627+
ssock, _ = lsock.accept()
628+
except:
629+
csock.close()
630+
raise
631+
finally:
632+
lsock.close()
596633

597-
def socketpair(family=None, type=SOCK_STREAM, proto=0):
598-
"""socketpair([family[, type[, proto]]]) -> (socket object, socket object)
634+
# Authenticating avoids using a connection from something else
635+
# able to connect to {host}:{port} instead of us.
636+
# We expect only AF_INET and AF_INET6 families.
637+
try:
638+
if (
639+
ssock.getsockname() != csock.getpeername()
640+
or csock.getsockname() != ssock.getpeername()
641+
):
642+
raise ConnectionError("Unexpected peer connection")
643+
except:
644+
# getsockname() and getpeername() can fail
645+
# if either socket isn't connected.
646+
ssock.close()
647+
csock.close()
648+
raise
599649

600-
Create a pair of socket objects from the sockets returned by the platform
601-
socketpair() function.
602-
The arguments are the same as for socket() except the default family is
603-
AF_UNIX if defined on the platform; otherwise, the default is AF_INET.
604-
"""
650+
return (ssock, csock)
651+
652+
if hasattr(_socket, "socketpair"):
653+
def socketpair(family=None, type=SOCK_STREAM, proto=0):
605654
if family is None:
606655
try:
607656
family = AF_UNIX
@@ -613,61 +662,7 @@ def socketpair(family=None, type=SOCK_STREAM, proto=0):
613662
return a, b
614663

615664
else:
616-
617-
# Origin: https://gist.github.com/4325783, by Geert Jansen. Public domain.
618-
def socketpair(family=AF_INET, type=SOCK_STREAM, proto=0):
619-
if family == AF_INET:
620-
host = _LOCALHOST
621-
elif family == AF_INET6:
622-
host = _LOCALHOST_V6
623-
else:
624-
raise ValueError("Only AF_INET and AF_INET6 socket address families "
625-
"are supported")
626-
if type != SOCK_STREAM:
627-
raise ValueError("Only SOCK_STREAM socket type is supported")
628-
if proto != 0:
629-
raise ValueError("Only protocol zero is supported")
630-
631-
# We create a connected TCP socket. Note the trick with
632-
# setblocking(False) that prevents us from having to create a thread.
633-
lsock = socket(family, type, proto)
634-
try:
635-
lsock.bind((host, 0))
636-
lsock.listen()
637-
# On IPv6, ignore flow_info and scope_id
638-
addr, port = lsock.getsockname()[:2]
639-
csock = socket(family, type, proto)
640-
try:
641-
csock.setblocking(False)
642-
try:
643-
csock.connect((addr, port))
644-
except (BlockingIOError, InterruptedError):
645-
pass
646-
csock.setblocking(True)
647-
ssock, _ = lsock.accept()
648-
except:
649-
csock.close()
650-
raise
651-
finally:
652-
lsock.close()
653-
654-
# Authenticating avoids using a connection from something else
655-
# able to connect to {host}:{port} instead of us.
656-
# We expect only AF_INET and AF_INET6 families.
657-
try:
658-
if (
659-
ssock.getsockname() != csock.getpeername()
660-
or csock.getsockname() != ssock.getpeername()
661-
):
662-
raise ConnectionError("Unexpected peer connection")
663-
except:
664-
# getsockname() and getpeername() can fail
665-
# if either socket isn't connected.
666-
ssock.close()
667-
csock.close()
668-
raise
669-
670-
return (ssock, csock)
665+
socketpair = _fallback_socketpair
671666
__all__.append("socketpair")
672667

673668
socketpair.__doc__ = """socketpair([family[, type[, proto]]]) -> (socket object, socket object)

Lib/test/test_socket.py

+6-14
Original file line numberDiff line numberDiff line change
@@ -4861,7 +4861,6 @@ def _testSend(self):
48614861

48624862

48634863
class PurePythonSocketPairTest(SocketPairTest):
4864-
48654864
# Explicitly use socketpair AF_INET or AF_INET6 to ensure that is the
48664865
# code path we're using regardless platform is the pure python one where
48674866
# `_socket.socketpair` does not exist. (AF_INET does not work with
@@ -4876,28 +4875,21 @@ def socketpair(self):
48764875
# Local imports in this class make for easy security fix backporting.
48774876

48784877
def setUp(self):
4879-
import _socket
4880-
self._orig_sp = getattr(_socket, 'socketpair', None)
4881-
if self._orig_sp is not None:
4878+
if hasattr(_socket, "socketpair"):
4879+
self._orig_sp = socket.socketpair
48824880
# This forces the version using the non-OS provided socketpair
48834881
# emulation via an AF_INET socket in Lib/socket.py.
4884-
del _socket.socketpair
4885-
import importlib
4886-
global socket
4887-
socket = importlib.reload(socket)
4882+
socket.socketpair = socket._fallback_socketpair
48884883
else:
4889-
pass # This platform already uses the non-OS provided version.
4884+
# This platform already uses the non-OS provided version.
4885+
self._orig_sp = None
48904886
super().setUp()
48914887

48924888
def tearDown(self):
48934889
super().tearDown()
4894-
import _socket
48954890
if self._orig_sp is not None:
48964891
# Restore the default socket.socketpair definition.
4897-
_socket.socketpair = self._orig_sp
4898-
import importlib
4899-
global socket
4900-
socket = importlib.reload(socket)
4892+
socket.socketpair = self._orig_sp
49014893

49024894
def test_recv(self):
49034895
msg = self.serv.recv(1024)

0 commit comments

Comments
 (0)