Skip to content

Commit a52a350

Browse files
authored
gh-109015: Add test.support.socket_helper.tcp_blackhole() (#109016)
Skip test_asyncio, test_imaplib and test_socket tests if FreeBSD TCP blackhole is enabled (net.inet.tcp.blackhole=2).
1 parent 60a9eea commit a52a350

File tree

7 files changed

+79
-0
lines changed

7 files changed

+79
-0
lines changed

Diff for: Lib/test/support/socket_helper.py

+60
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os.path
44
import socket
55
import sys
6+
import subprocess
67
import tempfile
78
import unittest
89

@@ -277,3 +278,62 @@ def create_unix_domain_name():
277278
"""
278279
return tempfile.mktemp(prefix="test_python_", suffix='.sock',
279280
dir=os.path.curdir)
281+
282+
283+
# consider that sysctl values should not change while tests are running
284+
_sysctl_cache = {}
285+
286+
def _get_sysctl(name):
287+
"""Get a sysctl value as an integer."""
288+
try:
289+
return _sysctl_cache[name]
290+
except KeyError:
291+
pass
292+
293+
# At least Linux and FreeBSD support the "-n" option
294+
cmd = ['sysctl', '-n', name]
295+
proc = subprocess.run(cmd,
296+
stdout=subprocess.PIPE,
297+
stderr=subprocess.STDOUT,
298+
text=True)
299+
if proc.returncode:
300+
support.print_warning(f'{' '.join(cmd)!r} command failed with '
301+
f'exit code {proc.returncode}')
302+
# cache the error to only log the warning once
303+
_sysctl_cache[name] = None
304+
return None
305+
output = proc.stdout
306+
307+
# Parse '0\n' to get '0'
308+
try:
309+
value = int(output.strip())
310+
except Exception as exc:
311+
support.print_warning(f'Failed to parse {' '.join(cmd)!r} '
312+
f'command output {output!r}: {exc!r}')
313+
# cache the error to only log the warning once
314+
_sysctl_cache[name] = None
315+
return None
316+
317+
_sysctl_cache[name] = value
318+
return value
319+
320+
321+
def tcp_blackhole():
322+
if not sys.platform.startswith('freebsd'):
323+
return False
324+
325+
# gh-109015: test if FreeBSD TCP blackhole is enabled
326+
value = _get_sysctl('net.inet.tcp.blackhole')
327+
if value is None:
328+
# don't skip if we fail to get the sysctl value
329+
return False
330+
return (value != 0)
331+
332+
333+
def skip_if_tcp_blackhole(test):
334+
"""Decorator skipping test if TCP blackhole is enabled."""
335+
skip_if = unittest.skipIf(
336+
tcp_blackhole(),
337+
"TCP blackhole is enabled (sysctl net.inet.tcp.blackhole)"
338+
)
339+
return skip_if(test)

Diff for: Lib/test/test_asyncio/test_events.py

+3
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ def test_create_connection_local_addr(self):
671671
self.assertEqual(port, expected)
672672
tr.close()
673673

674+
@socket_helper.skip_if_tcp_blackhole
674675
def test_create_connection_local_addr_skip_different_family(self):
675676
# See https://github.com/python/cpython/issues/86508
676677
port1 = socket_helper.find_unused_port()
@@ -692,6 +693,7 @@ async def getaddrinfo(host, port, *args, **kwargs):
692693
with self.assertRaises(OSError):
693694
self.loop.run_until_complete(f)
694695

696+
@socket_helper.skip_if_tcp_blackhole
695697
def test_create_connection_local_addr_nomatch_family(self):
696698
# See https://github.com/python/cpython/issues/86508
697699
port1 = socket_helper.find_unused_port()
@@ -1271,6 +1273,7 @@ def connection_made(self, transport):
12711273

12721274
server.close()
12731275

1276+
@socket_helper.skip_if_tcp_blackhole
12741277
def test_server_close(self):
12751278
f = self.loop.create_server(MyProto, '0.0.0.0', 0)
12761279
server = self.loop.run_until_complete(f)

Diff for: Lib/test/test_asyncio/test_sock_lowlevel.py

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
from test import support
1111
from test.support import socket_helper
1212

13+
if socket_helper.tcp_blackhole():
14+
raise unittest.SkipTest('Not relevant to ProactorEventLoop')
15+
16+
1317
def tearDownModule():
1418
asyncio.set_event_loop_policy(None)
1519

Diff for: Lib/test/test_asyncio/test_sslproto.py

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import unittest
66
import weakref
77
from test import support
8+
from test.support import socket_helper
89
from unittest import mock
910
try:
1011
import ssl
@@ -350,6 +351,7 @@ async def client(addr):
350351
support.gc_collect()
351352
self.assertIsNone(client_context())
352353

354+
@socket_helper.skip_if_tcp_blackhole
353355
def test_start_tls_client_buf_proto_1(self):
354356
HELLO_MSG = b'1' * self.PAYLOAD_SIZE
355357

@@ -502,6 +504,7 @@ async def client(addr):
502504
asyncio.wait_for(client(srv.addr),
503505
timeout=support.SHORT_TIMEOUT))
504506

507+
@socket_helper.skip_if_tcp_blackhole
505508
def test_start_tls_server_1(self):
506509
HELLO_MSG = b'1' * self.PAYLOAD_SIZE
507510
ANSWER = b'answer'

Diff for: Lib/test/test_imaplib.py

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def test_that_Time2Internaldate_returns_a_result(self):
7474
for t in self.timevalues():
7575
imaplib.Time2Internaldate(t)
7676

77+
@socket_helper.skip_if_tcp_blackhole
7778
def test_imap4_host_default_value(self):
7879
# Check whether the IMAP4_PORT is truly unavailable.
7980
with socket.socket() as s:

Diff for: Lib/test/test_socket.py

+2
Original file line numberDiff line numberDiff line change
@@ -5288,6 +5288,7 @@ def mocked_socket_module(self):
52885288
finally:
52895289
socket.socket = old_socket
52905290

5291+
@socket_helper.skip_if_tcp_blackhole
52915292
def test_connect(self):
52925293
port = socket_helper.find_unused_port()
52935294
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -5296,6 +5297,7 @@ def test_connect(self):
52965297
cli.connect((HOST, port))
52975298
self.assertEqual(cm.exception.errno, errno.ECONNREFUSED)
52985299

5300+
@socket_helper.skip_if_tcp_blackhole
52995301
def test_create_connection(self):
53005302
# Issue #9792: errors raised by create_connection() should have
53015303
# a proper errno attribute.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Fix test_asyncio, test_imaplib and test_socket tests on FreeBSD if the TCP
2+
blackhole is enabled (``sysctl net.inet.tcp.blackhole``). Skip the few tests
3+
which failed with ``ETIMEDOUT`` which such non standard configuration.
4+
Currently, the `FreeBSD GCP image enables TCP and UDP blackhole
5+
<https://reviews.freebsd.org/D41751>`_ (``sysctl net.inet.tcp.blackhole=2``
6+
and ``sysctl net.inet.udp.blackhole=1``). Patch by Victor Stinner.

0 commit comments

Comments
 (0)