]> granicus.if.org Git - python/commitdiff
Merged revisions 84597-84599 via svnmerge from
authorAntoine Pitrou <solipsis@pitrou.net>
Tue, 7 Sep 2010 21:40:25 +0000 (21:40 +0000)
committerAntoine Pitrou <solipsis@pitrou.net>
Tue, 7 Sep 2010 21:40:25 +0000 (21:40 +0000)
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.
........

Lib/socket.py
Lib/test/test_robotparser.py
Lib/test/test_socket.py
Lib/test/test_ssl.py
Lib/test/test_support.py
Lib/test/test_urllib2net.py
Misc/NEWS

index 9ed2de9132bbaf7a5b9cd90c50ae933e58986c3a..bd364e70db90effe786b65403a5cf2e48a403a39 100644 (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.
     """
 
-    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")
index aa73ec5663d9c078c4d54e97483b9c726a7d08f1..3376a8a3cb4b79ee55e6c91f8097b822c2bfaa52 100644 (file)
@@ -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():
index da8e155b0e179351cdf5112e916cc017423ec90e..5b5d4fb237ce8c91d4a31e10edec498cb0cb109b 100644 (file)
@@ -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):
index c32ee04b3817815207340d44fb35ed1cc68cbe6c..2cdef1668efe62c046aee84b4c98116af5e91643 100644 (file)
@@ -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:
index ce2b679ddd4aecefe5d75ed198bfc99888b89ee5..32ff970f7dc0521c6c450a5eab2e2006bf1e0e75 100644 (file)
@@ -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),
+    ]
+
+    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
 
-    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
-    """
+    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
index 4da38eff24d4783ba827c66d1cf2b71abf51acbe..76572b052b775da84d61524bc64318865a38c25f 100644 (file)
@@ -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:
index 2b5562cd976784fea09911d63372bff2721a0ed6..16304995e2b0043dc2e873b1fb4f1bac3746b74b 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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.