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:
Antoine Pitrou 2010-09-07 21:40:25 +00:00
parent d47a68716e
commit c818ed4d61
7 changed files with 111 additions and 47 deletions

View File

@ -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. An host of '' or port 0 tells the OS to use the default.
""" """
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
@ -562,8 +562,12 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
sock.connect(sa) sock.connect(sa)
return sock return sock
except error, msg: except error as _:
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")

View File

@ -232,23 +232,24 @@ class NetworkTestCase(unittest.TestCase):
def testPasswordProtectedSite(self): def testPasswordProtectedSite(self):
test_support.requires('network') test_support.requires('network')
# XXX it depends on an external resource which could be unavailable with test_support.transient_internet('mueblesmoraleda.com'):
url = 'http://mueblesmoraleda.com' url = 'http://mueblesmoraleda.com'
parser = robotparser.RobotFileParser() parser = robotparser.RobotFileParser()
parser.set_url(url) parser.set_url(url)
try: try:
parser.read() parser.read()
except IOError: except IOError:
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):
test_support.requires('network') test_support.requires('network')
parser = robotparser.RobotFileParser( with test_support.transient_internet('www.python.org'):
"http://www.python.org/robots.txt") parser = 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():

View File

@ -12,6 +12,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
@ -1048,12 +1049,42 @@ 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 = test_support.find_unused_port() port = test_support.find_unused_port()
self.assertRaises( cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.error, with self.assertRaises(socket.error) as cm:
lambda: socket.create_connection((HOST, port)) 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.') @unittest.skipUnless(thread, 'Threading required for this test.')
class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest): class NetworkConnectionAttributesTest(SocketTCPTest, ThreadableTest):

View File

@ -287,10 +287,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 test_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 test_support.transient_internet(): ca_certs=sha256_cert,)
try: try:
s.connect(remote) s.connect(remote)
if test_support.verbose: if test_support.verbose:

View File

@ -750,32 +750,55 @@ class TransientResource(object):
raise ResourceDenied("an optional resource is not available") 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 @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."""
default_errnos = [
('ECONNREFUSED', 111),
('ECONNRESET', 104),
('ENETUNREACH', 101),
('ETIMEDOUT', 110),
]
Errors caught: denied = ResourceDenied("Resource '%s' is not available" % resource_name)
timeout IOError errno = ETIMEDOUT captured_errnos = errnos
socket reset socket.error, IOError errno = ECONNRESET if not captured_errnos:
dns no data socket.gaierror errno = EAI_NODATA captured_errnos = [getattr(errno, name, num)
dns no name socket.gaierror errno = EAI_NONAME 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: try:
if timeout is not None:
socket.setdefaulttimeout(timeout)
yield yield
except tuple(_transients) as err: except IOError as err:
for errtype in _transients: # urllib can wrap original socket errors multiple times (!), we must
if isinstance(err, errtype) and err.errno in _transients[errtype]: # unwrap to get at the original error.
raise ResourceDenied("could not establish network " while True:
"connection ({})".format(err)) 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 raise
# XXX should we catch generic exceptions and look for their
# __cause__ or __context__?
finally:
socket.setdefaulttimeout(old_timeout)
@contextlib.contextmanager @contextlib.contextmanager

View File

@ -192,7 +192,7 @@ class OtherNetworkTests(unittest.TestCase):
raise raise
else: else:
try: try:
with test_support.transient_internet(): with test_support.transient_internet(url):
buf = f.read() buf = f.read()
debug("read %d bytes" % len(buf)) debug("read %d bytes" % len(buf))
except socket.timeout: except socket.timeout:

View File

@ -36,6 +36,11 @@ Core and Builtins
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.