Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ The :mod:`test.support` module defines the following constants:

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

Its default value is 1 minute.

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


.. function:: transient_internet(resource_name, *, timeout=30.0, errnos=())

A context manager that raises :exc:`ResourceDenied` when various issues
with the internet connection manifest themselves as exceptions.


.. function:: disable_faulthandler()

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


.. function:: transient_internet(resource_name, *, timeout=30.0, errnos=())

A context manager that raises :exc:`~test.support.ResourceDenied` when
various issues with the internet connection manifest themselves as
exceptions.


:mod:`test.support.script_helper` --- Utilities for the Python execution tests
==============================================================================

Expand Down
88 changes: 1 addition & 87 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"requires_linux_version", "requires_mac_ver",
"check_syntax_error", "check_syntax_warning",
"TransientResource", "time_out", "socket_peer_reset", "ioerror_peer_reset",
"transient_internet", "BasicTestRunner", "run_unittest", "run_doctest",
"BasicTestRunner", "run_unittest", "run_doctest",
"skip_unless_symlink", "requires_gzip", "requires_bz2", "requires_lzma",
"bigmemtest", "bigaddrspacetest", "cpython_only", "get_attribute",
"requires_IEEE_754", "skip_unless_xattr", "requires_zlib",
Expand Down Expand Up @@ -144,8 +144,6 @@
# option.
LONG_TIMEOUT = 5 * 60.0

_NOT_SET = object()


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


@contextlib.contextmanager
def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()):
"""Return a context manager that raises ResourceDenied when various issues
with the Internet connection manifest themselves as exceptions."""
import socket
import nntplib
import urllib.error
if timeout is _NOT_SET:
timeout = INTERNET_TIMEOUT

default_errnos = [
('ECONNREFUSED', 111),
('ECONNRESET', 104),
('EHOSTUNREACH', 113),
('ENETUNREACH', 101),
('ETIMEDOUT', 110),
# socket.create_connection() fails randomly with
# EADDRNOTAVAIL on Travis CI.
('EADDRNOTAVAIL', 99),
]
default_gai_errnos = [
('EAI_AGAIN', -3),
('EAI_FAIL', -4),
('EAI_NONAME', -2),
('EAI_NODATA', -5),
# Encountered when trying to resolve IPv6-only hostnames
('WSANO_DATA', 11004),
]

denied = ResourceDenied("Resource %r is not available" % resource_name)
captured_errnos = errnos
gai_errnos = []
if not captured_errnos:
captured_errnos = [getattr(errno, name, num)
for (name, num) in default_errnos]
gai_errnos = [getattr(socket, name, num)
for (name, num) in default_gai_errnos]

def filter_error(err):
n = getattr(err, 'errno', None)
if (isinstance(err, socket.timeout) or
(isinstance(err, socket.gaierror) and n in gai_errnos) or
(isinstance(err, urllib.error.HTTPError) and
500 <= err.code <= 599) or
(isinstance(err, urllib.error.URLError) and
(("ConnectionRefusedError" in err.reason) or
("TimeoutError" in err.reason) or
("EOFError" in err.reason))) or
n in captured_errnos):
if not verbose:
sys.stderr.write(denied.args[0] + "\n")
raise denied from err

old_timeout = socket.getdefaulttimeout()
try:
if timeout is not None:
socket.setdefaulttimeout(timeout)
yield
except nntplib.NNTPTemporaryError as err:
if verbose:
sys.stderr.write(denied.args[0] + "\n")
raise denied from err
except OSError as err:
# urllib can wrap original socket errors multiple times (!), we must
# unwrap to get at the original error.
while True:
a = err.args
if len(a) >= 1 and isinstance(a[0], OSError):
err = a[0]
# The error can also be wrapped as args[1]:
# except socket.error as msg:
# raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
elif len(a) >= 2 and isinstance(a[1], OSError):
err = a[1]
else:
break
filter_error(err)
raise
# XXX should we catch generic exceptions and look for their
# __cause__ or __context__?
finally:
socket.setdefaulttimeout(old_timeout)


@contextlib.contextmanager
def captured_output(stream_name):
"""Return a context manager used by captured_stdout/stdin/stderr
Expand Down
89 changes: 89 additions & 0 deletions Lib/test/support/socket_helper.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import contextlib
import errno
import socket
import unittest

from .. import support


HOST = "localhost"
HOSTv4 = "127.0.0.1"
HOSTv6 = "::1"
Expand Down Expand Up @@ -175,3 +179,88 @@ def get_socket_conn_refused_errs():
if not IPV6_ENABLED:
errors.append(errno.EAFNOSUPPORT)
return errors


_NOT_SET = object()

@contextlib.contextmanager
def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()):
"""Return a context manager that raises ResourceDenied when various issues
with the Internet connection manifest themselves as exceptions."""
import nntplib
import urllib.error
if timeout is _NOT_SET:
timeout = support.INTERNET_TIMEOUT

default_errnos = [
('ECONNREFUSED', 111),
('ECONNRESET', 104),
('EHOSTUNREACH', 113),
('ENETUNREACH', 101),
('ETIMEDOUT', 110),
# socket.create_connection() fails randomly with
# EADDRNOTAVAIL on Travis CI.
('EADDRNOTAVAIL', 99),
]
default_gai_errnos = [
('EAI_AGAIN', -3),
('EAI_FAIL', -4),
('EAI_NONAME', -2),
('EAI_NODATA', -5),
# Encountered when trying to resolve IPv6-only hostnames
('WSANO_DATA', 11004),
]

denied = support.ResourceDenied("Resource %r is not available" % resource_name)
captured_errnos = errnos
gai_errnos = []
if not captured_errnos:
captured_errnos = [getattr(errno, name, num)
for (name, num) in default_errnos]
gai_errnos = [getattr(socket, name, num)
for (name, num) in default_gai_errnos]

def filter_error(err):
n = getattr(err, 'errno', None)
if (isinstance(err, socket.timeout) or
(isinstance(err, socket.gaierror) and n in gai_errnos) or
(isinstance(err, urllib.error.HTTPError) and
500 <= err.code <= 599) or
(isinstance(err, urllib.error.URLError) and
(("ConnectionRefusedError" in err.reason) or
("TimeoutError" in err.reason) or
("EOFError" in err.reason))) or
n in captured_errnos):
if not support.verbose:
sys.stderr.write(denied.args[0] + "\n")
raise denied from err

old_timeout = socket.getdefaulttimeout()
try:
if timeout is not None:
socket.setdefaulttimeout(timeout)
yield
except nntplib.NNTPTemporaryError as err:
if support.verbose:
sys.stderr.write(denied.args[0] + "\n")
raise denied from err
except OSError as err:
# urllib can wrap original socket errors multiple times (!), we must
# unwrap to get at the original error.
while True:
a = err.args
if len(a) >= 1 and isinstance(a[0], OSError):
err = a[0]
# The error can also be wrapped as args[1]:
# except socket.error as msg:
# raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
elif len(a) >= 2 and isinstance(a[1], OSError):
err = a[1]
else:
break
filter_error(err)
raise
# XXX should we catch generic exceptions and look for their
# __cause__ or __context__?
finally:
socket.setdefaulttimeout(old_timeout)
10 changes: 5 additions & 5 deletions Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ def test_networked(self):
# Default settings: requires a valid cert from a trusted CA
import ssl
support.requires('network')
with support.transient_internet('self-signed.pythontest.net'):
with socket_helper.transient_internet('self-signed.pythontest.net'):
h = client.HTTPSConnection('self-signed.pythontest.net', 443)
with self.assertRaises(ssl.SSLError) as exc_info:
h.request('GET', '/')
Expand All @@ -1639,7 +1639,7 @@ def test_networked_noverification(self):
# Switch off cert verification
import ssl
support.requires('network')
with support.transient_internet('self-signed.pythontest.net'):
with socket_helper.transient_internet('self-signed.pythontest.net'):
context = ssl._create_unverified_context()
h = client.HTTPSConnection('self-signed.pythontest.net', 443,
context=context)
Expand All @@ -1653,7 +1653,7 @@ def test_networked_noverification(self):
def test_networked_trusted_by_default_cert(self):
# Default settings: requires a valid cert from a trusted CA
support.requires('network')
with support.transient_internet('www.python.org'):
with socket_helper.transient_internet('www.python.org'):
h = client.HTTPSConnection('www.python.org', 443)
h.request('GET', '/')
resp = h.getresponse()
Expand All @@ -1667,7 +1667,7 @@ def test_networked_good_cert(self):
import ssl
support.requires('network')
selfsigned_pythontestdotnet = 'self-signed.pythontest.net'
with support.transient_internet(selfsigned_pythontestdotnet):
with socket_helper.transient_internet(selfsigned_pythontestdotnet):
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
self.assertEqual(context.verify_mode, ssl.CERT_REQUIRED)
self.assertEqual(context.check_hostname, True)
Expand Down Expand Up @@ -1699,7 +1699,7 @@ def test_networked_bad_cert(self):
# We feed a "CA" cert that is unrelated to the server's cert
import ssl
support.requires('network')
with support.transient_internet('self-signed.pythontest.net'):
with socket_helper.transient_internet('self-signed.pythontest.net'):
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(CERT_localhost)
h = client.HTTPSConnection('self-signed.pythontest.net', 443, context=context)
Expand Down
20 changes: 10 additions & 10 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import threading
import socket

from test.support import (reap_threads, verbose, transient_internet,
from test.support import (reap_threads, verbose,
run_with_tz, run_with_locale, cpython_only)
from test.support import hashlib_helper
import unittest
Expand Down Expand Up @@ -968,16 +968,16 @@ class RemoteIMAPTest(unittest.TestCase):
imap_class = imaplib.IMAP4

def setUp(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
self.server = self.imap_class(self.host, self.port)

def tearDown(self):
if self.server is not None:
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
self.server.logout()

def test_logincapa(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
for cap in self.server.capabilities:
self.assertIsInstance(cap, str)
self.assertIn('LOGINDISABLED', self.server.capabilities)
Expand All @@ -986,7 +986,7 @@ def test_logincapa(self):
self.assertEqual(rs[0], 'OK')

def test_logout(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
rs = self.server.logout()
self.server = None
self.assertEqual(rs[0], 'BYE', rs)
Expand All @@ -999,7 +999,7 @@ class RemoteIMAP_STARTTLSTest(RemoteIMAPTest):

def setUp(self):
super().setUp()
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
rs = self.server.starttls()
self.assertEqual(rs[0], 'OK')

Expand Down Expand Up @@ -1039,24 +1039,24 @@ def check_logincapa(self, server):
server.logout()

def test_logincapa(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
_server = self.imap_class(self.host, self.port)
self.check_logincapa(_server)

def test_logout(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
_server = self.imap_class(self.host, self.port)
rs = _server.logout()
self.assertEqual(rs[0], 'BYE', rs)

def test_ssl_context_certfile_exclusive(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
self.assertRaises(
ValueError, self.imap_class, self.host, self.port,
certfile=CERTFILE, ssl_context=self.create_ssl_context())

def test_ssl_context_keyfile_exclusive(self):
with transient_internet(self.host):
with socket_helper.transient_internet(self.host):
self.assertRaises(
ValueError, self.imap_class, self.host, self.port,
keyfile=CERTFILE, ssl_context=self.create_ssl_context())
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_nntplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ def wrap_methods(cls):
def wrap_meth(meth):
@functools.wraps(meth)
def wrapped(self):
with support.transient_internet(self.NNTP_HOST):
with socket_helper.transient_internet(self.NNTP_HOST):
meth(self)
return wrapped
for name in dir(cls):
Expand Down Expand Up @@ -315,7 +315,7 @@ class NetworkedNNTPTests(NetworkedNNTPTestsMixin, unittest.TestCase):
@classmethod
def setUpClass(cls):
support.requires("network")
with support.transient_internet(cls.NNTP_HOST):
with socket_helper.transient_internet(cls.NNTP_HOST):
try:
cls.server = cls.NNTP_CLASS(cls.NNTP_HOST,
timeout=support.INTERNET_TIMEOUT,
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_robotparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ class NetworkTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls):
support.requires('network')
with support.transient_internet(cls.base_url):
with socket_helper.transient_internet(cls.base_url):
cls.parser = urllib.robotparser.RobotFileParser(cls.robots_txt)
cls.parser.read()

Expand Down
Loading