mirror of https://github.com/python/cpython
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
d47a68716e
commit
c818ed4d61
|
@ -548,8 +548,8 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
|||
An host of '' or port 0 tells the OS to use the default.
|
||||
"""
|
||||
|
||||
msg = "getaddrinfo returns an empty list"
|
||||
host, port = address
|
||||
err = None
|
||||
for res in getaddrinfo(host, port, 0, SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
sock = None
|
||||
|
@ -562,8 +562,12 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
|
|||
sock.connect(sa)
|
||||
return sock
|
||||
|
||||
except error, msg:
|
||||
except error as _:
|
||||
err = _
|
||||
if sock is not None:
|
||||
sock.close()
|
||||
|
||||
raise error, msg
|
||||
if err is not None:
|
||||
raise err
|
||||
else:
|
||||
raise error("getaddrinfo returns an empty list")
|
||||
|
|
|
@ -232,23 +232,24 @@ class NetworkTestCase(unittest.TestCase):
|
|||
|
||||
def testPasswordProtectedSite(self):
|
||||
test_support.requires('network')
|
||||
# XXX it depends on an external resource which could be unavailable
|
||||
url = 'http://mueblesmoraleda.com'
|
||||
parser = robotparser.RobotFileParser()
|
||||
parser.set_url(url)
|
||||
try:
|
||||
parser.read()
|
||||
except IOError:
|
||||
self.skipTest('%s is unavailable' % url)
|
||||
self.assertEqual(parser.can_fetch("*", url+"/robots.txt"), False)
|
||||
with test_support.transient_internet('mueblesmoraleda.com'):
|
||||
url = 'http://mueblesmoraleda.com'
|
||||
parser = robotparser.RobotFileParser()
|
||||
parser.set_url(url)
|
||||
try:
|
||||
parser.read()
|
||||
except IOError:
|
||||
self.skipTest('%s is unavailable' % url)
|
||||
self.assertEqual(parser.can_fetch("*", url+"/robots.txt"), False)
|
||||
|
||||
def testPythonOrg(self):
|
||||
test_support.requires('network')
|
||||
parser = robotparser.RobotFileParser(
|
||||
"http://www.python.org/robots.txt")
|
||||
parser.read()
|
||||
self.assertTrue(parser.can_fetch("*",
|
||||
"http://www.python.org/robots.txt"))
|
||||
with test_support.transient_internet('www.python.org'):
|
||||
parser = robotparser.RobotFileParser(
|
||||
"http://www.python.org/robots.txt")
|
||||
parser.read()
|
||||
self.assertTrue(
|
||||
parser.can_fetch("*", "http://www.python.org/robots.txt"))
|
||||
|
||||
|
||||
def test_main():
|
||||
|
|
|
@ -12,6 +12,7 @@ import Queue
|
|||
import sys
|
||||
import os
|
||||
import array
|
||||
import contextlib
|
||||
from weakref import proxy
|
||||
import signal
|
||||
|
||||
|
@ -1048,12 +1049,42 @@ class BasicTCPTest2(NetworkConnectionTest, BasicTCPTest):
|
|||
"""
|
||||
|
||||
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 = test_support.find_unused_port()
|
||||
self.assertRaises(
|
||||
socket.error,
|
||||
lambda: socket.create_connection((HOST, port))
|
||||
)
|
||||
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
with self.assertRaises(socket.error) as cm:
|
||||
cli.connect((HOST, port))
|
||||
self.assertEqual(cm.exception.errno, errno.ECONNREFUSED)
|
||||
|
||||
def test_create_connection(self):
|
||||
# Issue #9792: errors raised by create_connection() should have
|
||||
# a proper errno attribute.
|
||||
port = test_support.find_unused_port()
|
||||
with self.assertRaises(socket.error) as cm:
|
||||
socket.create_connection((HOST, port))
|
||||
self.assertEqual(cm.exception.errno, errno.ECONNREFUSED)
|
||||
|
||||
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))
|
||||
|
||||
|
||||
@unittest.skipUnless(thread, 'Threading required for this test.')
|
||||
class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):
|
||||
|
|
|
@ -287,10 +287,10 @@ class NetworkedTests(unittest.TestCase):
|
|||
# NOTE: https://sha256.tbs-internet.com is another possible test host
|
||||
remote = ("sha2.hboeck.de", 443)
|
||||
sha256_cert = os.path.join(os.path.dirname(__file__), "sha256.pem")
|
||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||
cert_reqs=ssl.CERT_REQUIRED,
|
||||
ca_certs=sha256_cert,)
|
||||
with test_support.transient_internet():
|
||||
with test_support.transient_internet("sha2.hboeck.de"):
|
||||
s = ssl.wrap_socket(socket.socket(socket.AF_INET),
|
||||
cert_reqs=ssl.CERT_REQUIRED,
|
||||
ca_certs=sha256_cert,)
|
||||
try:
|
||||
s.connect(remote)
|
||||
if test_support.verbose:
|
||||
|
|
|
@ -750,32 +750,55 @@ class TransientResource(object):
|
|||
raise ResourceDenied("an optional resource is not available")
|
||||
|
||||
|
||||
_transients = {
|
||||
IOError: (errno.ECONNRESET, errno.ETIMEDOUT),
|
||||
socket.error: (errno.ECONNRESET,),
|
||||
socket.gaierror: [getattr(socket, t)
|
||||
for t in ('EAI_NODATA', 'EAI_NONAME')
|
||||
if hasattr(socket, t)],
|
||||
}
|
||||
@contextlib.contextmanager
|
||||
def transient_internet():
|
||||
def transient_internet(resource_name, timeout=30.0, errnos=()):
|
||||
"""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."""
|
||||
default_errnos = [
|
||||
('ECONNREFUSED', 111),
|
||||
('ECONNRESET', 104),
|
||||
('ENETUNREACH', 101),
|
||||
('ETIMEDOUT', 110),
|
||||
]
|
||||
|
||||
Errors caught:
|
||||
timeout IOError errno = ETIMEDOUT
|
||||
socket reset socket.error, IOError errno = ECONNRESET
|
||||
dns no data socket.gaierror errno = EAI_NODATA
|
||||
dns no name socket.gaierror errno = EAI_NONAME
|
||||
"""
|
||||
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
|
||||
|
||||
old_timeout = socket.getdefaulttimeout()
|
||||
try:
|
||||
if timeout is not None:
|
||||
socket.setdefaulttimeout(timeout)
|
||||
yield
|
||||
except tuple(_transients) as err:
|
||||
for errtype in _transients:
|
||||
if isinstance(err, errtype) and err.errno in _transients[errtype]:
|
||||
raise ResourceDenied("could not establish network "
|
||||
"connection ({})".format(err))
|
||||
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
|
||||
|
|
|
@ -192,7 +192,7 @@ class OtherNetworkTests(unittest.TestCase):
|
|||
raise
|
||||
else:
|
||||
try:
|
||||
with test_support.transient_internet():
|
||||
with test_support.transient_internet(url):
|
||||
buf = f.read()
|
||||
debug("read %d bytes" % len(buf))
|
||||
except socket.timeout:
|
||||
|
|
|
@ -36,6 +36,11 @@ Core and Builtins
|
|||
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,
|
||||
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.
|
||||
|
|
Loading…
Reference in New Issue