Skip to content

Commit bfb1cf4

Browse files
bpo-40275: Move transient_internet from test.support to socket_helper (GH-19711)
1 parent bb4a585 commit bfb1cf4

15 files changed

+153
-144
lines changed

Doc/library/test.rst

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ The :mod:`test.support` module defines the following constants:
314314

315315
Usually, a timeout using :data:`INTERNET_TIMEOUT` should not mark a test as
316316
failed, but skip the test instead: see
317-
:func:`~test.support.transient_internet`.
317+
:func:`~test.support.socket_helper.transient_internet`.
318318

319319
Its default value is 1 minute.
320320

@@ -759,12 +759,6 @@ The :mod:`test.support` module defines the following functions:
759759
A context manager that temporarily sets the process umask.
760760

761761

762-
.. function:: transient_internet(resource_name, *, timeout=30.0, errnos=())
763-
764-
A context manager that raises :exc:`ResourceDenied` when various issues
765-
with the internet connection manifest themselves as exceptions.
766-
767-
768762
.. function:: disable_faulthandler()
769763

770764
A context manager that replaces ``sys.stderr`` with ``sys.__stderr__``.
@@ -1488,6 +1482,13 @@ The :mod:`test.support.socket_helper` module provides support for socket tests.
14881482
sockets.
14891483

14901484

1485+
.. function:: transient_internet(resource_name, *, timeout=30.0, errnos=())
1486+
1487+
A context manager that raises :exc:`~test.support.ResourceDenied` when
1488+
various issues with the internet connection manifest themselves as
1489+
exceptions.
1490+
1491+
14911492
:mod:`test.support.script_helper` --- Utilities for the Python execution tests
14921493
==============================================================================
14931494

Lib/test/support/__init__.py

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878
"requires_linux_version", "requires_mac_ver",
7979
"check_syntax_error", "check_syntax_warning",
8080
"TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
81-
"transient_internet", "BasicTestRunner", "run_unittest", "run_doctest",
81+
"BasicTestRunner", "run_unittest", "run_doctest",
8282
"skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
8383
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
8484
"requires_IEEE_754", "skip_unless_xattr", "requires_zlib",
@@ -144,8 +144,6 @@
144144
# option.
145145
LONG_TIMEOUT = 5 * 60.0
146146

147-
_NOT_SET = object()
148-
149147

150148
class Error(Exception):
151149
"""Base class for regression test exceptions."""
@@ -1386,90 +1384,6 @@ def __exit__(self, type_=None, value=None, traceback=None):
13861384
ioerror_peer_reset = TransientResource(OSError, errno=errno.ECONNRESET)
13871385

13881386

1389-
@contextlib.contextmanager
1390-
def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()):
1391-
"""Return a context manager that raises ResourceDenied when various issues
1392-
with the Internet connection manifest themselves as exceptions."""
1393-
import socket
1394-
import nntplib
1395-
import urllib.error
1396-
if timeout is _NOT_SET:
1397-
timeout = INTERNET_TIMEOUT
1398-
1399-
default_errnos = [
1400-
('ECONNREFUSED', 111),
1401-
('ECONNRESET', 104),
1402-
('EHOSTUNREACH', 113),
1403-
('ENETUNREACH', 101),
1404-
('ETIMEDOUT', 110),
1405-
# socket.create_connection() fails randomly with
1406-
# EADDRNOTAVAIL on Travis CI.
1407-
('EADDRNOTAVAIL', 99),
1408-
]
1409-
default_gai_errnos = [
1410-
('EAI_AGAIN', -3),
1411-
('EAI_FAIL', -4),
1412-
('EAI_NONAME', -2),
1413-
('EAI_NODATA', -5),
1414-
# Encountered when trying to resolve IPv6-only hostnames
1415-
('WSANO_DATA', 11004),
1416-
]
1417-
1418-
denied = ResourceDenied("Resource %r is not available" % resource_name)
1419-
captured_errnos = errnos
1420-
gai_errnos = []
1421-
if not captured_errnos:
1422-
captured_errnos = [getattr(errno, name, num)
1423-
for (name, num) in default_errnos]
1424-
gai_errnos = [getattr(socket, name, num)
1425-
for (name, num) in default_gai_errnos]
1426-
1427-
def filter_error(err):
1428-
n = getattr(err, 'errno', None)
1429-
if (isinstance(err, socket.timeout) or
1430-
(isinstance(err, socket.gaierror) and n in gai_errnos) or
1431-
(isinstance(err, urllib.error.HTTPError) and
1432-
500 <= err.code <= 599) or
1433-
(isinstance(err, urllib.error.URLError) and
1434-
(("ConnectionRefusedError" in err.reason) or
1435-
("TimeoutError" in err.reason) or
1436-
("EOFError" in err.reason))) or
1437-
n in captured_errnos):
1438-
if not verbose:
1439-
sys.stderr.write(denied.args[0] + "\n")
1440-
raise denied from err
1441-
1442-
old_timeout = socket.getdefaulttimeout()
1443-
try:
1444-
if timeout is not None:
1445-
socket.setdefaulttimeout(timeout)
1446-
yield
1447-
except nntplib.NNTPTemporaryError as err:
1448-
if verbose:
1449-
sys.stderr.write(denied.args[0] + "\n")
1450-
raise denied from err
1451-
except OSError as err:
1452-
# urllib can wrap original socket errors multiple times (!), we must
1453-
# unwrap to get at the original error.
1454-
while True:
1455-
a = err.args
1456-
if len(a) >= 1 and isinstance(a[0], OSError):
1457-
err = a[0]
1458-
# The error can also be wrapped as args[1]:
1459-
# except socket.error as msg:
1460-
# raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
1461-
elif len(a) >= 2 and isinstance(a[1], OSError):
1462-
err = a[1]
1463-
else:
1464-
break
1465-
filter_error(err)
1466-
raise
1467-
# XXX should we catch generic exceptions and look for their
1468-
# __cause__ or __context__?
1469-
finally:
1470-
socket.setdefaulttimeout(old_timeout)
1471-
1472-
14731387
@contextlib.contextmanager
14741388
def captured_output(stream_name):
14751389
"""Return a context manager used by captured_stdout/stdin/stderr

Lib/test/support/socket_helper.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import contextlib
12
import errno
23
import socket
34
import unittest
45

6+
from .. import support
7+
8+
59
HOST = "localhost"
610
HOSTv4 = "127.0.0.1"
711
HOSTv6 = "::1"
@@ -175,3 +179,88 @@ def get_socket_conn_refused_errs():
175179
if not IPV6_ENABLED:
176180
errors.append(errno.EAFNOSUPPORT)
177181
return errors
182+
183+
184+
_NOT_SET = object()
185+
186+
@contextlib.contextmanager
187+
def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()):
188+
"""Return a context manager that raises ResourceDenied when various issues
189+
with the Internet connection manifest themselves as exceptions."""
190+
import nntplib
191+
import urllib.error
192+
if timeout is _NOT_SET:
193+
timeout = support.INTERNET_TIMEOUT
194+
195+
default_errnos = [
196+
('ECONNREFUSED', 111),
197+
('ECONNRESET', 104),
198+
('EHOSTUNREACH', 113),
199+
('ENETUNREACH', 101),
200+
('ETIMEDOUT', 110),
201+
# socket.create_connection() fails randomly with
202+
# EADDRNOTAVAIL on Travis CI.
203+
('EADDRNOTAVAIL', 99),
204+
]
205+
default_gai_errnos = [
206+
('EAI_AGAIN', -3),
207+
('EAI_FAIL', -4),
208+
('EAI_NONAME', -2),
209+
('EAI_NODATA', -5),
210+
# Encountered when trying to resolve IPv6-only hostnames
211+
('WSANO_DATA', 11004),
212+
]
213+
214+
denied = support.ResourceDenied("Resource %r is not available" % resource_name)
215+
captured_errnos = errnos
216+
gai_errnos = []
217+
if not captured_errnos:
218+
captured_errnos = [getattr(errno, name, num)
219+
for (name, num) in default_errnos]
220+
gai_errnos = [getattr(socket, name, num)
221+
for (name, num) in default_gai_errnos]
222+
223+
def filter_error(err):
224+
n = getattr(err, 'errno', None)
225+
if (isinstance(err, socket.timeout) or
226+
(isinstance(err, socket.gaierror) and n in gai_errnos) or
227+
(isinstance(err, urllib.error.HTTPError) and
228+
500 <= err.code <= 599) or
229+
(isinstance(err, urllib.error.URLError) and
230+
(("ConnectionRefusedError" in err.reason) or
231+
("TimeoutError" in err.reason) or
232+
("EOFError" in err.reason))) or
233+
n in captured_errnos):
234+
if not support.verbose:
235+
sys.stderr.write(denied.args[0] + "\n")
236+
raise denied from err
237+
238+
old_timeout = socket.getdefaulttimeout()
239+
try:
240+
if timeout is not None:
241+
socket.setdefaulttimeout(timeout)
242+
yield
243+
except nntplib.NNTPTemporaryError as err:
244+
if support.verbose:
245+
sys.stderr.write(denied.args[0] + "\n")
246+
raise denied from err
247+
except OSError as err:
248+
# urllib can wrap original socket errors multiple times (!), we must
249+
# unwrap to get at the original error.
250+
while True:
251+
a = err.args
252+
if len(a) >= 1 and isinstance(a[0], OSError):
253+
err = a[0]
254+
# The error can also be wrapped as args[1]:
255+
# except socket.error as msg:
256+
# raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
257+
elif len(a) >= 2 and isinstance(a[1], OSError):
258+
err = a[1]
259+
else:
260+
break
261+
filter_error(err)
262+
raise
263+
# XXX should we catch generic exceptions and look for their
264+
# __cause__ or __context__?
265+
finally:
266+
socket.setdefaulttimeout(old_timeout)

Lib/test/test_httplib.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1629,7 +1629,7 @@ def test_networked(self):
16291629
# Default settings: requires a valid cert from a trusted CA
16301630
import ssl
16311631
support.requires('network')
1632-
with support.transient_internet('self-signed.pythontest.net'):
1632+
with socket_helper.transient_internet('self-signed.pythontest.net'):
16331633
h = client.HTTPSConnection('self-signed.pythontest.net', 443)
16341634
with self.assertRaises(ssl.SSLError) as exc_info:
16351635
h.request('GET', '/')
@@ -1639,7 +1639,7 @@ def test_networked_noverification(self):
16391639
# Switch off cert verification
16401640
import ssl
16411641
support.requires('network')
1642-
with support.transient_internet('self-signed.pythontest.net'):
1642+
with socket_helper.transient_internet('self-signed.pythontest.net'):
16431643
context = ssl._create_unverified_context()
16441644
h = client.HTTPSConnection('self-signed.pythontest.net', 443,
16451645
context=context)
@@ -1653,7 +1653,7 @@ def test_networked_noverification(self):
16531653
def test_networked_trusted_by_default_cert(self):
16541654
# Default settings: requires a valid cert from a trusted CA
16551655
support.requires('network')
1656-
with support.transient_internet('www.python.org'):
1656+
with socket_helper.transient_internet('www.python.org'):
16571657
h = client.HTTPSConnection('www.python.org', 443)
16581658
h.request('GET', '/')
16591659
resp = h.getresponse()
@@ -1667,7 +1667,7 @@ def test_networked_good_cert(self):
16671667
import ssl
16681668
support.requires('network')
16691669
selfsigned_pythontestdotnet = 'self-signed.pythontest.net'
1670-
with support.transient_internet(selfsigned_pythontestdotnet):
1670+
with socket_helper.transient_internet(selfsigned_pythontestdotnet):
16711671
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
16721672
self.assertEqual(context.verify_mode, ssl.CERT_REQUIRED)
16731673
self.assertEqual(context.check_hostname, True)
@@ -1699,7 +1699,7 @@ def test_networked_bad_cert(self):
16991699
# We feed a "CA" cert that is unrelated to the server's cert
17001700
import ssl
17011701
support.requires('network')
1702-
with support.transient_internet('self-signed.pythontest.net'):
1702+
with socket_helper.transient_internet('self-signed.pythontest.net'):
17031703
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
17041704
context.load_verify_locations(CERT_localhost)
17051705
h = client.HTTPSConnection('self-signed.pythontest.net', 443, context=context)

Lib/test/test_imaplib.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import threading
1111
import socket
1212

13-
from test.support import (reap_threads, verbose, transient_internet,
13+
from test.support import (reap_threads, verbose,
1414
run_with_tz, run_with_locale, cpython_only)
1515
from test.support import hashlib_helper
1616
import unittest
@@ -968,16 +968,16 @@ class RemoteIMAPTest(unittest.TestCase):
968968
imap_class = imaplib.IMAP4
969969

970970
def setUp(self):
971-
with transient_internet(self.host):
971+
with socket_helper.transient_internet(self.host):
972972
self.server = self.imap_class(self.host, self.port)
973973

974974
def tearDown(self):
975975
if self.server is not None:
976-
with transient_internet(self.host):
976+
with socket_helper.transient_internet(self.host):
977977
self.server.logout()
978978

979979
def test_logincapa(self):
980-
with transient_internet(self.host):
980+
with socket_helper.transient_internet(self.host):
981981
for cap in self.server.capabilities:
982982
self.assertIsInstance(cap, str)
983983
self.assertIn('LOGINDISABLED', self.server.capabilities)
@@ -986,7 +986,7 @@ def test_logincapa(self):
986986
self.assertEqual(rs[0], 'OK')
987987

988988
def test_logout(self):
989-
with transient_internet(self.host):
989+
with socket_helper.transient_internet(self.host):
990990
rs = self.server.logout()
991991
self.server = None
992992
self.assertEqual(rs[0], 'BYE', rs)
@@ -999,7 +999,7 @@ class RemoteIMAP_STARTTLSTest(RemoteIMAPTest):
999999

10001000
def setUp(self):
10011001
super().setUp()
1002-
with transient_internet(self.host):
1002+
with socket_helper.transient_internet(self.host):
10031003
rs = self.server.starttls()
10041004
self.assertEqual(rs[0], 'OK')
10051005

@@ -1039,24 +1039,24 @@ def check_logincapa(self, server):
10391039
server.logout()
10401040

10411041
def test_logincapa(self):
1042-
with transient_internet(self.host):
1042+
with socket_helper.transient_internet(self.host):
10431043
_server = self.imap_class(self.host, self.port)
10441044
self.check_logincapa(_server)
10451045

10461046
def test_logout(self):
1047-
with transient_internet(self.host):
1047+
with socket_helper.transient_internet(self.host):
10481048
_server = self.imap_class(self.host, self.port)
10491049
rs = _server.logout()
10501050
self.assertEqual(rs[0], 'BYE', rs)
10511051

10521052
def test_ssl_context_certfile_exclusive(self):
1053-
with transient_internet(self.host):
1053+
with socket_helper.transient_internet(self.host):
10541054
self.assertRaises(
10551055
ValueError, self.imap_class, self.host, self.port,
10561056
certfile=CERTFILE, ssl_context=self.create_ssl_context())
10571057

10581058
def test_ssl_context_keyfile_exclusive(self):
1059-
with transient_internet(self.host):
1059+
with socket_helper.transient_internet(self.host):
10601060
self.assertRaises(
10611061
ValueError, self.imap_class, self.host, self.port,
10621062
keyfile=CERTFILE, ssl_context=self.create_ssl_context())

Lib/test/test_nntplib.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def wrap_methods(cls):
246246
def wrap_meth(meth):
247247
@functools.wraps(meth)
248248
def wrapped(self):
249-
with support.transient_internet(self.NNTP_HOST):
249+
with socket_helper.transient_internet(self.NNTP_HOST):
250250
meth(self)
251251
return wrapped
252252
for name in dir(cls):
@@ -315,7 +315,7 @@ class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase):
315315
@classmethod
316316
def setUpClass(cls):
317317
support.requires("network")
318-
with support.transient_internet(cls.NNTP_HOST):
318+
with socket_helper.transient_internet(cls.NNTP_HOST):
319319
try:
320320
cls.server = cls.NNTP_CLASS(cls.NNTP_HOST,
321321
timeout=support.INTERNET_TIMEOUT,

Lib/test/test_robotparser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ class NetworkTestCase(unittest.TestCase):
349349
@classmethod
350350
def setUpClass(cls):
351351
support.requires('network')
352-
with support.transient_internet(cls.base_url):
352+
with socket_helper.transient_internet(cls.base_url):
353353
cls.parser = urllib.robotparser.RobotFileParser(cls.robots_txt)
354354
cls.parser.read()
355355

0 commit comments

Comments
 (0)