mirror of
https://github.com/python/cpython.git
synced 2025-07-31 07:04:42 +00:00
Merged revisions 84597-84599 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/branches/py3k ........ r84597 | antoine.pitrou | 2010-09-07 22:42:19 +0200 (mar., 07 sept. 2010) | 5 lines Issue #8574: better implementation of test.support.transient_internet(). Original patch by Victor. ........ r84598 | antoine.pitrou | 2010-09-07 23:05:49 +0200 (mar., 07 sept. 2010) | 6 lines Issue #9792: In case of connection failure, socket.create_connection() would swallow the exception and raise a new one, making it impossible to fetch the original errno, or to filter timeout errors. Now the original error is re-raised. ........ r84599 | antoine.pitrou | 2010-09-07 23:09:09 +0200 (mar., 07 sept. 2010) | 4 lines Improve transient_internet() again to detect more network errors, and use it in test_robotparser. Fixes #8574. ........
This commit is contained in:
parent
824cf253e5
commit
4d7979be72
6 changed files with 134 additions and 46 deletions
|
@ -287,8 +287,8 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
|
||||||
is used.
|
is used.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
msg = "getaddrinfo returns an empty list"
|
|
||||||
host, port = address
|
host, port = address
|
||||||
|
err = None
|
||||||
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
|
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
|
||||||
af, socktype, proto, canonname, sa = res
|
af, socktype, proto, canonname, sa = res
|
||||||
sock = None
|
sock = None
|
||||||
|
@ -299,9 +299,12 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT):
|
||||||
sock.connect(sa)
|
sock.connect(sa)
|
||||||
return sock
|
return sock
|
||||||
|
|
||||||
except error as err:
|
except error as _:
|
||||||
msg = err
|
err = _
|
||||||
if sock is not None:
|
if sock is not None:
|
||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
raise error(msg)
|
if err is not None:
|
||||||
|
raise err
|
||||||
|
else:
|
||||||
|
raise error("getaddrinfo returns an empty list")
|
||||||
|
|
|
@ -17,19 +17,21 @@ import unittest
|
||||||
import importlib
|
import importlib
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
__all__ = ["Error", "TestFailed", "ResourceDenied", "import_module",
|
__all__ = [
|
||||||
"verbose", "use_resources", "max_memuse", "record_original_stdout",
|
"Error", "TestFailed", "ResourceDenied", "import_module",
|
||||||
"get_original_stdout", "unload", "unlink", "rmtree", "forget",
|
"verbose", "use_resources", "max_memuse", "record_original_stdout",
|
||||||
"is_resource_enabled", "requires", "find_unused_port", "bind_port",
|
"get_original_stdout", "unload", "unlink", "rmtree", "forget",
|
||||||
"fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "findfile", "verify",
|
"is_resource_enabled", "requires", "find_unused_port", "bind_port",
|
||||||
"vereq", "sortdict", "check_syntax_error", "open_urlresource",
|
"fcmp", "is_jython", "TESTFN", "HOST", "FUZZ", "findfile", "verify",
|
||||||
"check_warnings", "CleanImport", "EnvironmentVarGuard",
|
"vereq", "sortdict", "check_syntax_error", "open_urlresource",
|
||||||
"TransientResource", "captured_output", "captured_stdout",
|
"check_warnings", "CleanImport", "EnvironmentVarGuard",
|
||||||
"time_out", "socket_peer_reset", "ioerror_peer_reset",
|
"TransientResource", "captured_output", "captured_stdout",
|
||||||
"run_with_locale",
|
"time_out", "socket_peer_reset", "ioerror_peer_reset",
|
||||||
"set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner",
|
"run_with_locale", "transient_internet",
|
||||||
"run_unittest", "run_doctest", "threading_setup", "threading_cleanup",
|
"set_memlimit", "bigmemtest", "bigaddrspacetest", "BasicTestRunner",
|
||||||
"reap_children", "cpython_only", "check_impl_detail", "get_attribute"]
|
"run_unittest", "run_doctest", "threading_setup", "threading_cleanup",
|
||||||
|
"reap_children", "cpython_only", "check_impl_detail", "get_attribute",
|
||||||
|
]
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
"""Base class for regression test exceptions."""
|
"""Base class for regression test exceptions."""
|
||||||
|
@ -604,23 +606,63 @@ class TransientResource(object):
|
||||||
else:
|
else:
|
||||||
raise ResourceDenied("an optional resource is not available")
|
raise ResourceDenied("an optional resource is not available")
|
||||||
|
|
||||||
|
|
||||||
# Context managers that raise ResourceDenied when various issues
|
# Context managers that raise ResourceDenied when various issues
|
||||||
# with the Internet connection manifest themselves as exceptions.
|
# with the Internet connection manifest themselves as exceptions.
|
||||||
|
# XXX deprecate these and use transient_internet() instead
|
||||||
time_out = TransientResource(IOError, errno=errno.ETIMEDOUT)
|
time_out = TransientResource(IOError, errno=errno.ETIMEDOUT)
|
||||||
socket_peer_reset = TransientResource(socket.error, errno=errno.ECONNRESET)
|
socket_peer_reset = TransientResource(socket.error, errno=errno.ECONNRESET)
|
||||||
ioerror_peer_reset = TransientResource(IOError, errno=errno.ECONNRESET)
|
ioerror_peer_reset = TransientResource(IOError, errno=errno.ECONNRESET)
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def transient_internet():
|
def transient_internet(resource_name, *, timeout=30.0, errnos=()):
|
||||||
"""Return a context manager that raises ResourceDenied when various issues
|
"""Return a context manager that raises ResourceDenied when various issues
|
||||||
with the Internet connection manifest themselves as exceptions."""
|
with the Internet connection manifest themselves as exceptions."""
|
||||||
time_out = TransientResource(IOError, errno=errno.ETIMEDOUT)
|
default_errnos = [
|
||||||
socket_peer_reset = TransientResource(socket.error, errno=errno.ECONNRESET)
|
('ECONNREFUSED', 111),
|
||||||
ioerror_peer_reset = TransientResource(IOError, errno=errno.ECONNRESET)
|
('ECONNRESET', 104),
|
||||||
with time_out, socket_peer_reset, ioerror_peer_reset:
|
('ENETUNREACH', 101),
|
||||||
|
('ETIMEDOUT', 110),
|
||||||
|
]
|
||||||
|
|
||||||
|
denied = ResourceDenied("Resource '%s' is not available" % resource_name)
|
||||||
|
captured_errnos = errnos
|
||||||
|
if not captured_errnos:
|
||||||
|
captured_errnos = [getattr(errno, name, num)
|
||||||
|
for (name, num) in default_errnos]
|
||||||
|
|
||||||
|
def filter_error(err):
|
||||||
|
if (isinstance(err, socket.timeout) or
|
||||||
|
getattr(err, 'errno', None) 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
|
yield
|
||||||
|
except IOError 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], IOError):
|
||||||
|
err = a[0]
|
||||||
|
# The error can also be wrapped as args[1]:
|
||||||
|
# except socket.error as msg:
|
||||||
|
# raise IOError('socket error', msg).with_traceback(sys.exc_info()[2])
|
||||||
|
elif len(a) >= 2 and isinstance(a[1], IOError):
|
||||||
|
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
|
@contextlib.contextmanager
|
||||||
|
|
|
@ -235,23 +235,24 @@ class NetworkTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def testPasswordProtectedSite(self):
|
def testPasswordProtectedSite(self):
|
||||||
support.requires('network')
|
support.requires('network')
|
||||||
# XXX it depends on an external resource which could be unavailable
|
with support.transient_internet('mueblesmoraleda.com'):
|
||||||
url = 'http://mueblesmoraleda.com'
|
url = 'http://mueblesmoraleda.com'
|
||||||
parser = urllib.robotparser.RobotFileParser()
|
parser = urllib.robotparser.RobotFileParser()
|
||||||
parser.set_url(url)
|
parser.set_url(url)
|
||||||
try:
|
try:
|
||||||
parser.read()
|
parser.read()
|
||||||
except URLError:
|
except URLError:
|
||||||
self.skipTest('%s is unavailable' % url)
|
self.skipTest('%s is unavailable' % url)
|
||||||
self.assertEqual(parser.can_fetch("*", url+"/robots.txt"), False)
|
self.assertEqual(parser.can_fetch("*", url+"/robots.txt"), False)
|
||||||
|
|
||||||
def testPythonOrg(self):
|
def testPythonOrg(self):
|
||||||
support.requires('network')
|
support.requires('network')
|
||||||
parser = urllib.robotparser.RobotFileParser(
|
with support.transient_internet('www.python.org'):
|
||||||
"http://www.python.org/robots.txt")
|
parser = urllib.robotparser.RobotFileParser(
|
||||||
parser.read()
|
"http://www.python.org/robots.txt")
|
||||||
self.assertTrue(parser.can_fetch("*",
|
parser.read()
|
||||||
"http://www.python.org/robots.txt"))
|
self.assertTrue(
|
||||||
|
parser.can_fetch("*", "http://www.python.org/robots.txt"))
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(NetworkTestCase)
|
support.run_unittest(NetworkTestCase)
|
||||||
|
|
|
@ -14,6 +14,7 @@ import queue
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import array
|
import array
|
||||||
|
import contextlib
|
||||||
from weakref import proxy
|
from weakref import proxy
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
@ -1026,12 +1027,48 @@ class BasicTCPTest2(NetworkConnectionTest, BasicTCPTest):
|
||||||
|
|
||||||
class NetworkConnectionNoServer(unittest.TestCase):
|
class NetworkConnectionNoServer(unittest.TestCase):
|
||||||
|
|
||||||
def testWithoutServer(self):
|
class MockSocket(socket.socket):
|
||||||
|
def connect(self, *args):
|
||||||
|
raise socket.timeout('timed out')
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def mocked_socket_module(self):
|
||||||
|
"""Return a socket which times out on connect"""
|
||||||
|
old_socket = socket.socket
|
||||||
|
socket.socket = self.MockSocket
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
socket.socket = old_socket
|
||||||
|
|
||||||
|
def test_connect(self):
|
||||||
port = support.find_unused_port()
|
port = support.find_unused_port()
|
||||||
self.assertRaises(
|
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
socket.error,
|
try:
|
||||||
lambda: socket.create_connection((HOST, port))
|
cli.connect((HOST, port))
|
||||||
)
|
except socket.error as err:
|
||||||
|
self.assertEqual(err.errno, errno.ECONNREFUSED)
|
||||||
|
else:
|
||||||
|
self.fail("socket.error not raised")
|
||||||
|
|
||||||
|
def test_create_connection(self):
|
||||||
|
# Issue #9792: errors raised by create_connection() should have
|
||||||
|
# a proper errno attribute.
|
||||||
|
port = support.find_unused_port()
|
||||||
|
try:
|
||||||
|
socket.create_connection((HOST, port))
|
||||||
|
except socket.error as err:
|
||||||
|
self.assertEqual(err.errno, errno.ECONNREFUSED)
|
||||||
|
else:
|
||||||
|
self.fail("socket.error not raised")
|
||||||
|
|
||||||
|
def test_create_connection_timeout(self):
|
||||||
|
# Issue #9792: create_connection() should not recast timeout errors
|
||||||
|
# as generic socket errors.
|
||||||
|
with self.mocked_socket_module():
|
||||||
|
with self.assertRaises(socket.timeout):
|
||||||
|
socket.create_connection((HOST, 1234))
|
||||||
|
|
||||||
|
|
||||||
class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):
|
class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):
|
||||||
|
|
||||||
|
|
|
@ -218,10 +218,10 @@ class NetworkedTests(unittest.TestCase):
|
||||||
# NOTE: https://sha256.tbs-internet.com is another possible test host
|
# NOTE: https://sha256.tbs-internet.com is another possible test host
|
||||||
remote = ("sha2.hboeck.de", 443)
|
remote = ("sha2.hboeck.de", 443)
|
||||||
sha256_cert = os.path.join(os.path.dirname(__file__), "sha256.pem")
|
sha256_cert = os.path.join(os.path.dirname(__file__), "sha256.pem")
|
||||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
with support.transient_internet("sha2.hboeck.de"):
|
||||||
cert_reqs=ssl.CERT_REQUIRED,
|
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||||
ca_certs=sha256_cert,)
|
cert_reqs=ssl.CERT_REQUIRED,
|
||||||
with support.transient_internet():
|
ca_certs=sha256_cert,)
|
||||||
try:
|
try:
|
||||||
s.connect(remote)
|
s.connect(remote)
|
||||||
if support.verbose:
|
if support.verbose:
|
||||||
|
|
|
@ -105,6 +105,11 @@ C-API
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #9792: In case of connection failure, socket.create_connection()
|
||||||
|
would swallow the exception and raise a new one, making it impossible
|
||||||
|
to fetch the original errno, or to filter timeout errors. Now the
|
||||||
|
original error is re-raised.
|
||||||
|
|
||||||
- Issue #9758: When fcntl.ioctl() was called with mutable_flag set to True,
|
- Issue #9758: When fcntl.ioctl() was called with mutable_flag set to True,
|
||||||
and the passed buffer was exactly 1024 bytes long, the buffer wouldn't
|
and the passed buffer was exactly 1024 bytes long, the buffer wouldn't
|
||||||
be updated back after the system call. Original patch by Brian Brazil.
|
be updated back after the system call. Original patch by Brian Brazil.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue