From 43499b75307cd1e1335b524102108ad06d33a5be Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 6 Sep 2023 02:35:17 +0200 Subject: [PATCH 1/3] gh-109015: Add test.support.socket_helper.tcp_blackhole() Skip test_asyncio, test_imaplib and test_socket tests if FreeBSD TCP blackhole is enabled (net.inet.tcp.blackhole=2). --- Lib/test/support/socket_helper.py | 60 +++++++++++++++++++ Lib/test/test_asyncio/test_events.py | 3 + Lib/test/test_asyncio/test_sock_lowlevel.py | 4 ++ Lib/test/test_asyncio/test_sslproto.py | 3 + Lib/test/test_imaplib.py | 1 + Lib/test/test_socket.py | 2 + ...-09-06-18-27-53.gh-issue-109015.1dS1AQ.rst | 6 ++ 7 files changed, 79 insertions(+) create mode 100644 Misc/NEWS.d/next/Tests/2023-09-06-18-27-53.gh-issue-109015.1dS1AQ.rst diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py index 45f6d65c355dd4..98d85d355c7b2a 100644 --- a/Lib/test/support/socket_helper.py +++ b/Lib/test/support/socket_helper.py @@ -3,6 +3,7 @@ import os.path import socket import sys +import subprocess import tempfile import unittest @@ -277,3 +278,62 @@ def create_unix_domain_name(): """ return tempfile.mktemp(prefix="test_python_", suffix='.sock', dir=os.path.curdir) + + +# consider that sysctl values should not change while tests are running +_sysctl_cache = {} + +def _get_sysctl(name): + """Get a sysctl value as an integer.""" + try: + return _sysctl_cache[name] + except KeyError: + pass + + cmd = ['sysctl', name] + proc = subprocess.run(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True) + if proc.returncode: + support.print_warning(f'{' '.join(cmd)!r} command failed with ' + f'exit code {proc.returncode}') + # cache the error to only log the warning once + _sysctl_cache[name] = None + return None + output = proc.stdout + + # Parse 'net.inet.tcp.blackhole: 0\n' to get '0' + try: + value = output.rstrip().split(':', 1)[-1].strip() + value = int(value) + except Exception as exc: + support.print_warning(f'Failed to parse {' '.join(cmd)!r} ' + f'command output {output!r}: {exc!r}') + # cache the error to only log the warning once + _sysctl_cache[name] = None + return None + + _sysctl_cache[name] = value + return value + + +def tcp_blackhole(): + if not sys.platform.startswith('freebsd'): + return False + + # gh-109015: test if FreeBSD TCP blackhole is enabled + value = _get_sysctl('net.inet.tcp.blackhole') + if value is None: + # don't skip if we fail to get the sysctl value + return False + return (value != 0) + + +def skip_if_tcp_blackhole(test): + """Decorator skipping test if TCP blackhole is enabled.""" + skip_if = unittest.skipIf( + tcp_blackhole(), + "TCP blackhole is enabled (sysctl net.inet.tcp.blackhole)" + ) + return skip_if(test) diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index b9069056c3a436..30cc8fd80bfe94 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -671,6 +671,7 @@ def test_create_connection_local_addr(self): self.assertEqual(port, expected) tr.close() + @socket_helper.skip_if_tcp_blackhole def test_create_connection_local_addr_skip_different_family(self): # See https://github.com/python/cpython/issues/86508 port1 = socket_helper.find_unused_port() @@ -692,6 +693,7 @@ async def getaddrinfo(host, port, *args, **kwargs): with self.assertRaises(OSError): self.loop.run_until_complete(f) + @socket_helper.skip_if_tcp_blackhole def test_create_connection_local_addr_nomatch_family(self): # See https://github.com/python/cpython/issues/86508 port1 = socket_helper.find_unused_port() @@ -1271,6 +1273,7 @@ def connection_made(self, transport): server.close() + @socket_helper.skip_if_tcp_blackhole def test_server_close(self): f = self.loop.create_server(MyProto, '0.0.0.0', 0) server = self.loop.run_until_complete(f) diff --git a/Lib/test/test_asyncio/test_sock_lowlevel.py b/Lib/test/test_asyncio/test_sock_lowlevel.py index b829fd4cc69fff..075113cbe8e4a6 100644 --- a/Lib/test/test_asyncio/test_sock_lowlevel.py +++ b/Lib/test/test_asyncio/test_sock_lowlevel.py @@ -10,6 +10,10 @@ from test import support from test.support import socket_helper +if socket_helper.tcp_blackhole(): + raise unittest.SkipTest('Not relevant to ProactorEventLoop') + + def tearDownModule(): asyncio.set_event_loop_policy(None) diff --git a/Lib/test/test_asyncio/test_sslproto.py b/Lib/test/test_asyncio/test_sslproto.py index 52a45f1c7c6e96..37d015339761c6 100644 --- a/Lib/test/test_asyncio/test_sslproto.py +++ b/Lib/test/test_asyncio/test_sslproto.py @@ -5,6 +5,7 @@ import unittest import weakref from test import support +from test.support import socket_helper from unittest import mock try: import ssl @@ -350,6 +351,7 @@ async def client(addr): support.gc_collect() self.assertIsNone(client_context()) + @socket_helper.skip_if_tcp_blackhole def test_start_tls_client_buf_proto_1(self): HELLO_MSG = b'1' * self.PAYLOAD_SIZE @@ -502,6 +504,7 @@ async def client(addr): asyncio.wait_for(client(srv.addr), timeout=support.SHORT_TIMEOUT)) + @socket_helper.skip_if_tcp_blackhole def test_start_tls_server_1(self): HELLO_MSG = b'1' * self.PAYLOAD_SIZE ANSWER = b'answer' diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 89cdca499f92a9..a1eaf2169fe227 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -74,6 +74,7 @@ def test_that_Time2Internaldate_returns_a_result(self): for t in self.timevalues(): imaplib.Time2Internaldate(t) + @socket_helper.skip_if_tcp_blackhole def test_imap4_host_default_value(self): # Check whether the IMAP4_PORT is truly unavailable. with socket.socket() as s: diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 0eaf64257c3b81..f35618e0281e70 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -5288,6 +5288,7 @@ def mocked_socket_module(self): finally: socket.socket = old_socket + @socket_helper.skip_if_tcp_blackhole def test_connect(self): port = socket_helper.find_unused_port() cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -5296,6 +5297,7 @@ def test_connect(self): cli.connect((HOST, port)) self.assertEqual(cm.exception.errno, errno.ECONNREFUSED) + @socket_helper.skip_if_tcp_blackhole def test_create_connection(self): # Issue #9792: errors raised by create_connection() should have # a proper errno attribute. diff --git a/Misc/NEWS.d/next/Tests/2023-09-06-18-27-53.gh-issue-109015.1dS1AQ.rst b/Misc/NEWS.d/next/Tests/2023-09-06-18-27-53.gh-issue-109015.1dS1AQ.rst new file mode 100644 index 00000000000000..cb641be9312e1a --- /dev/null +++ b/Misc/NEWS.d/next/Tests/2023-09-06-18-27-53.gh-issue-109015.1dS1AQ.rst @@ -0,0 +1,6 @@ +Fix test_asyncio, test_imaplib and test_socket tests on FreeBSD if the TCP +blackhole is enabled (``sysctl net.inet.tcp.blackhole``). Skip the few tests +which failed with ``ETIMEDOUT`` which such non standard configuration. +Currently, the `FreeBSD GCP image enables TCP and UDP blackhole +`_ (``sysctl net.inet.tcp.blackhole=2`` +and ``sysctl net.inet.udp.blackhole=1``). Patch by Victor Stinner. From 410b2d3e6a3d1233115890eaecefafb7242a9297 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 6 Sep 2023 21:40:33 +0200 Subject: [PATCH 2/3] Use -n option suggested by Ed --- Lib/test/support/socket_helper.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py index 98d85d355c7b2a..bdf54cf1921558 100644 --- a/Lib/test/support/socket_helper.py +++ b/Lib/test/support/socket_helper.py @@ -290,7 +290,8 @@ def _get_sysctl(name): except KeyError: pass - cmd = ['sysctl', name] + # At least Linux and FreeBSD support the "-n" option + cmd = ['sysctl', '-n', name] proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -305,8 +306,7 @@ def _get_sysctl(name): # Parse 'net.inet.tcp.blackhole: 0\n' to get '0' try: - value = output.rstrip().split(':', 1)[-1].strip() - value = int(value) + value = int(output.strip()) except Exception as exc: support.print_warning(f'Failed to parse {' '.join(cmd)!r} ' f'command output {output!r}: {exc!r}') From 4eea5efa05cb5a9c0d04b5201ff9352e7e448d30 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 7 Sep 2023 00:59:32 +0200 Subject: [PATCH 3/3] Update comment --- Lib/test/support/socket_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/support/socket_helper.py b/Lib/test/support/socket_helper.py index bdf54cf1921558..87941ee1791b4e 100644 --- a/Lib/test/support/socket_helper.py +++ b/Lib/test/support/socket_helper.py @@ -304,7 +304,7 @@ def _get_sysctl(name): return None output = proc.stdout - # Parse 'net.inet.tcp.blackhole: 0\n' to get '0' + # Parse '0\n' to get '0' try: value = int(output.strip()) except Exception as exc: